目录
1:semphore的使用说明
参考这篇博客:https://www.cnblogs.com/crazymakercircle/p/13907012.html
2:semphore的使用案例
package com.saytoyou.com.thread; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; public class Student implements Runnable { private String studentName; private Semaphore semaphore; /** * 学生构造函数 * * @param studentName * @param semaphore */ public Student(String studentName, Semaphore semaphore) { this.studentName = studentName; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("我是"+studentName+"开始排队"); System.out.println("我是"+studentName + "开始打饭了" ); TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println("打饭完成" + studentName); semaphore.release(); } } }
package com.saytoyou.com.thread; import java.util.concurrent.Semaphore; public class SemaphoreTest { public static void main(String[] args) { //模拟学生食堂打饭,设置三个窗口同时执行 Semaphore semaphore = new Semaphore(3,true); for (int i = 0; i < 100; i++) { new Thread(new Student("学生" + i, semaphore)).start(); } } }
3:semphore的源码分析
3.1:构造函数分析
public Semaphore(int permits, boolean fair) { sync = fair ? new FairSync(permits) : new NonfairSync(permits); }
构造semphore,permits是线程的个数,fair设置公平锁和非公平锁
3.2:acquire方法源码分析
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException { final Node node = addWaiter(Node.SHARED); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head) { int r = tryAcquireShared(arg); if (r >= 0) { setHeadAndPropagate(node, r); p.next = null; // help GC failed = false; return; } } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
分析:
1:首先将节点放入等待队列中,返回当前节点,注意第一次的头节点是new空节点,不是当前节点
2:拿到当前节点的前驱节点,判断前驱节点是否为头节点,如果为头节点则尝试去获取资源,获取成功当前state减一,返回剩余state,并尝试唤醒后续节点
如果不是头节点,则放入FIFO阻塞队列,等待后续唤醒
3:加入state的初始值为3,那么前三个线程都可以进入获取资源,后面的就需要等待线程释放,唤醒
4:第一次node节点进入,head是新节点,指向第一个节点,第二个节点跟这进来,状态改为signal,第三个节点同上,第一个节点进入后获取资源并唤醒第二个,第二个把头介点设置为自己,接着唤醒第三个,后面stagte资源不足了,就需要不断循环尝试获取了
3.3:tryAcquireShared源码分析
final int nonfairTryAcquireShared(int acquires) { for (;;) { int available = getState(); int remaining = available - acquires; if (remaining < 0 || compareAndSetState(available, remaining)) return remaining; } }
分析:获取当前state的值,拿当前的state减去请求数,一般都是1,重新设置state的值,返回state的最新值
结合上面的分析为:进来之后进入阻塞队列中,判断节点的前驱节点是不是head如果是,就尝试获取资源,获取成功之后,唤醒后续节点
流程图如下:
4:释放资源源码分析
protected final boolean tryReleaseShared(int releases) { for (;;) { int current = getState(); int next = current + releases; if (next < current) // overflow throw new Error("Maximum permit count exceeded"); if (compareAndSetState(current, next)) return true; } }
释放资源,state的值加一,这样后续第四个线程被唤醒,其实是第一个线程释放state,然后唤醒第四个线程的
4.1 唤醒后续的等待线程
private void doReleaseShared() { for (;;) { Node h = head; if (h != null && h != tail) { int ws = h.waitStatus; if (ws == Node.SIGNAL) { if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) continue; // loop to recheck cases unparkSuccessor(h); } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) continue; // loop on failed CAS } if (h == head) // loop if head changed break; } }
此时head是指向第三个线程的,状态为signal,唤醒后一个线程
原创文章,作者:3628473679,如若转载,请注明出处:https://blog.ytso.com/244276.html