一、简介
Semaphore是一个计数信号量,是并发包中提供的用于控制某资源同时被访问的个数,它的本质是一个有个数限制的”共享锁”。 信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时, 线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可证。 当Semaphore只有一个许可和ReentrantLock独占锁类似,只能有一个线程对资源进行访问。Semaphore的实现是基于AQS的共享模式进行实现,对AbstractQueuedSynchronizer不清楚的, 可以看下我的另一篇对AbstractQueuedSynchronizer源码分析juejin.cn/post/684490…。基于Java8。
二、属性
//序列化和反序列化的版本号,①这样可以防止序列化和反序列化java动态的计算,在对象较大时可以提升一定的性能②其实是为了在类修改时,也能反序列化成功等等,对序列化和反序列化不清楚的可以网上学习下
private static final long serialVersionUID = -3222578661600680210L;
//Semaphore的实现是基于AQS的共享模式进行实现,Semaphore信号量许可证的获取和释放都是基于Sync,Sync是AbstractQueuedSynchronizer的子类,Sync在Semaphore中有两种实现类:NonfairSync(非公平获取许可)、FairSync(公平获取许可),Sync在下面内部类中进行介绍
private final Sync sync;三、内部类
- Sync内部类
//AbstractQueuedSynchronizer的实现类,Semaphore信号量许可证的获取和释放都是基于Sync,对AbstractQueuedSynchronizer不清楚的,可以看下另一篇AbstractQueuedSynchronizer源码分析 abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = 1192457210091910933L; //传入Semaphore信号量的许可证个数,(即AQS的状态,表示控制某资源同时被访问的个数,即允许permits个线程同时访问资源) Sync(int permits) { //设置AQS的属性state值 setState(permits); } //获取当前Semaphore信号量的许可证个数,即AQS中的state属性 final int getPermits() { //返回AQS的state属性值 return getState(); } /** * 非公平的获取许可证,在NonfairSync中使用到,非公平的获取Semaphore信号量的acquires个许可证 * * @param acquires 要获取Semaphore信号量的acquires个许可证 * @return 返回Semaphore信号量还剩余的许可证 */ final int nonfairTryAcquireShared(int acquires) { //循环的获取Semaphore信号量许可证,直到获取许可证成功,或者没有许可证可获取 for (;;) { //获取当前Semaphore信号量实例还剩余的许可证,即AQS的属性state值 int available = getState(); //当前Semaphore信号量实例可用的许可证个数减去要获取的acquires个许可证,得到剩余的许可证个数 int remaining = available - acquires; //计算出的剩余许可证个数小于0,表明要获取的许可证个数已经大于当前Semaphore信号量实例可用的许可证个数,或者获取许可证成功,使用CAS将许可证个数设置为计算出剩余的许可证个数 if (remaining < 0 || compareAndSetState(available, remaining)) //返回Semaphore信号量还剩余的许可证 return remaining; } } /** * 释放许可证,释放Semaphore信号量的releases个许可证 * * @param releases 要释放Semaphore信号量的releases个许可证 * @return 返回释放Semaphore信号量的releases个许可证是否成功 */ protected final boolean tryReleaseShared(int releases) { //循环,直到释放许可证成功,或者传入的要释放的许可证个数小于0,抛出错误 for (;;) { //获取当前Semaphore信号量实例还剩余的许可证,即AQS的属性state值 int current = getState(); //将当前Semaphore信号量实例还剩余的许可证个数和要释放Semaphore信号量的releases个许可证相加获取到新的许可证个数 int next = current + releases; //如果计算出的新的许可证个数小于当前Semaphore信号量实例还剩余的许可证个数,表明当前传入的releases是小于0的非法参数 if (next < current) // overflow //抛出Error错误 throw new Error("Maximum permit count exceeded"); //使用CAS将当前Semaphore信号量实例还剩余的许可证个数更新为计算出的新的许可证个数,如果失败重新进行循环 if (compareAndSetState(current, next)) //返回释放Semaphore信号量的releases个许可证成功 return true; } } /** * 减少Semaphore信号量的许可证个数,可能释放的许可证个数reductions大于Semaphore信号量还剩余的许可证个数,已经获取许可证的线程在释放许可证会抛出错误,或者新的线程要获取许可证,由于可用的许可证已经是负数,为此会抛出异常,感觉严谨的应该是需要将要减少的许可证个数和当前信号量剩余的许可证个数进行比较,如果大于返回错误或者不做任何操作 * * @param reductions 要将Semaphore信号量的许可证个数减少reductions的许可证个数 */ final void reducePermits(int reductions) { //循环,直到减少许可证成功,或者传入的要减少的许可证个数小于0,抛出错误 for (;;) { //获取当前Semaphore信号量实例还剩余的许可证个数,即AQS的属性state值 int current = getState(); //当前Semaphore信号量实例还剩余的许可证个数减去要减少reductions的许可证个数,计算出新的剩余许可证个数 int next = current - reductions; //如果计算出的新的许可证个数大于当前Semaphore信号量实例还剩余的许可证个数,表明当前传入的releases是小于0的非法参数 if (next > current) // underflow //抛出Error错误 throw new Error("Permit count underflow"); //使用CAS将当前Semaphore信号量实例还剩余的许可证个数更新为计算出的新的许可证个数,如果失败重新进行循环 if (compareAndSetState(current, next)) //返回 return; } } /** * 获取Semaphore信号量所有的剩余许可证 * * @param 返回获取到Semaphore信号量里的几个许可证 */ final int drainPermits() { //循环,直到获取到Semaphore信号量所有的剩余许可证个数,或者原先Semaphore信号量没有剩余许可证个数 for (;;) { //获取当前Semaphore信号量实例还剩余的许可证个数,即AQS的属性state值 int current = getState(); //如果当前Semaphore信号量实例没有剩余的许可证个数,或者成功获取Semaphore信号量所有的剩余许可证个数 if (current == 0 || compareAndSetState(current, 0)) //返回获取到Semaphore信号量里的几个许可证 return current; } } } - NonfairSync内部类
//非公平的获取Semaphore信号量许可证的Sync子类,只重写了AQS的一个模板方法tryAcquireShared static final class NonfairSync extends Sync { private static final long serialVersionUID = -2694183684443567898L; //传入Semaphore信号量的许可证个数,(即设置AQS的状态,表示控制某资源同时被访问的个数,即允许permits个线程同时访问资源) NonfairSync(int permits) { //调用上面介绍的Sync的构造函数 super(permits); } //NonfairSync重写了AbstractQueuedSynchronizer的tryAcquireShared模板方法,否则AQS中的tryAcquireShared方法会直接抛出UnsupportedOperationException异常 //所有的非公平获取信号量许可证都是基于此方法 //@param acquires 要获取的许可证个数 protected int tryAcquireShared(int acquires) { //nonfairTryAcquireShared方法,在上面内部类Sync中有进行介绍,非公平的Semaphore信号量许可证,无需判断同步队列中前面是否有节点也在获取Semaphore信号量许可证 return nonfairTryAcquireShared(acquires); } } - FairSync内部类
//公平的获取Semaphore信号量许可证的Sync子类,只重写了AQS的一个模板方法tryAcquireShared static final class FairSync extends Sync { private static final long serialVersionUID = 2014338818796000944L; //传入Semaphore信号量的许可证个数,(即设置AQS的状态,表示控制某资源同时被访问的个数,即允许permits个线程同时访问资源) FairSync(int permits) { //调用上面介绍的Sync的构造函数 super(permits); } //NonfairSync重写了AbstractQueuedSynchronizer的tryAcquireShared模板方法,否则AQS中的tryAcquireShared方法会直接抛出UnsupportedOperationException异常 //所有的公平获取信号量许可证都是基于此方法 //@param acquires 要获取的许可证个数 protected int tryAcquireShared(int acquires) { //循环的获取Semaphore信号量许可证,直到获取许可证成功,或者同步队列中有其他节点也在获取许可证,退出循环 for (;;) { //判断当前获取许可证的节点是否需要等待其他节点获取许可证成功,可以看下面对hasQueuedPredecessors方法的介绍 if (hasQueuedPredecessors()) //需要等待其他先前节点获取许可证,返回-1,表明调用此方法获取许可证失败 return -1; //获取当前Semaphore信号量实例还剩余的许可证个数,即AQS的属性state值 int available = getState(); //当前Semaphore信号量实例可用的许可证个数减去要获取的acquires个许可证,得到剩余的许可证个数 int remaining = available - acquires; //计算出的剩余许可证个数小于0,表明要获取的许可证个数已经大于当前Semaphore信号量实例可用的许可证个数,或者获取许可证成功,使用CAS将许可证个数设置为计算出剩余的许可证个数 if (remaining < 0 || compareAndSetState(available, remaining)) //返回Semaphore信号量还剩余的许可证 return remaining; } } } //FairSync从AbstractQueuedSynchronizer类中继承下来的hasQueuedPredecessors方法 public final boolean hasQueuedPredecessors() { //获取AQS中的同步队列尾节点 Node t = tail; //获取AQS中的同步队列头节点 Node h = head; //头节点的下一节点 Node s; //如果同步队列的头节点的下一节点中的线程不是当前线程,返回true,需要阻塞,或者头节点的下一节点为空,表明同步队列中还有其他节点,其他节点正在初始化头尾节点 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
四、构造函数
/**
* 根据传入的Semaphore信号量的许可证个数创建非公平的获取Semaphore信号量的许可证Semaphore实例
*
* @param permits Semaphore信号量的许可证个数
*/
public Semaphore(int permits) {
//调用上面非公平的获取Semaphore信号量的许可证的NonfairSync构造函数
sync = new NonfairSync(permits);
}
/**
* 根据传入的Semaphore信号量的许可证个数和是否公平的获取Semaphore信号量的许可证的fair标志位创建获取Semaphore信号量的许可证Semaphore实例
*
* @param fair true创建公平获取Semaphore信号量的许可证,false创建非公平获取Semaphore信号量的许可证
*/
public Semaphore(int permits, boolean fair) {
//true创建公平获取Semaphore信号量的许可证的FairSync,false创建非公平获取Semaphore信号量的许可证的NonfairSync
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}五、获取许可证
/**
* 获取Semaphore信号量的1个许可证,直到获取Semaphore信号量的1个许可证成功,支持中断,如果Semaphore信号量没有可用的许可证,线程会进入等待状态,直到Semaphore信号量有可用的许可证,如果线程在等待Semaphore信号量的1个许可证的过程中被其他线程中断,抛出InterruptedException异常
*/
public void acquire() throws InterruptedException {
//调用sync从AQS中继承下来的acquireSharedInterruptibly方法获取Semaphore信号量的1个许可证
sync.acquireSharedInterruptibly(1);
}
//Sync从AbstractQueuedSynchronizer继承下来的acquireSharedInterruptibly方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//检查线程是否有被中断
if (Thread.interrupted())
//有的话直接抛出中断异常
throw new InterruptedException();
//调用Sync重写AQS的tryAcquireShared方法,获取Semaphore信号量的许可证,详细的可以看下NonfairSync和FairSync的tryAcquireShared方法
if (tryAcquireShared(arg) < 0)
//如果tryAcquireShared获取Semaphore信号量的许可证失败,线程调用doAcquireSharedInterruptibly方法获取Semaphore信号量的许可证,如果Semaphore信号量没有可用的许可证,线程会进入等待状态,直到Semaphore信号量有可用的许可证,如果线程在等待Semaphore信号量的许可证的过程中被其他线程中断,抛出InterruptedException异常,doAcquireSharedInterruptibly详细的可以看下面的介绍
doAcquireSharedInterruptibly(arg);
}
//Sync从AbstractQueuedSynchronizer继承下来的doAcquireSharedInterruptibly方法
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//往同步队列中从尾节点加入一个模式为共享模式的节点,看下面对addWaiter方法的介绍
final Node node = addWaiter(Node.SHARED);
//执行是否失败的标志位,如果失败会将此节点从同步队列中移除
boolean failed = true;
try {
//死循环的获取Semaphore信号量的许可证成功,即AQS的state属性值是否大于等于arg,或者线程在等待Semaphore信号量有可用许可证的过程中,被中断,抛出中断异常
for (;;) {
//获取当前节点的前一个节点,前一个节点不存在抛出空指针异常,CLH(同步队列一定会有个有效的前置节点),predecessor方法会在下面进行介绍
final Node p = node.predecessor();
//如果当前节点的前置节点为表头
if (p == head) {
//使用上面介绍的调用Sync重写AQS的tryAcquireShared方法获取Semaphore信号量的arg个许可证,如果r大于0表示获取arg个许可证成功
int r = tryAcquireShared(arg);
//如果r大于0,即获取arg个许可证成功
if (r >= 0) {
//设置当前节点为头节点,并且唤醒等待Semaphore信号量有可用许可证的下一个节点,可以看下面对setHeadAndPropagate方法的介绍
setHeadAndPropagate(node, r);
//将当前节点的前置节点下一个节点置为空,即当前节点的前置节点从同步队列中移除
p.next = null; // help GC
//线程获取arg个许可证成功
failed = false;
//直接返回
return;
}
}
//判断当前节点的前置节点的状态值是否为SIGNAL,如果是调用parkAndCheckInterrupt方法阻塞当前线程,shouldParkAfterFailedAcquire和parkAndCheckInterrupt方法可以看下面对这两个方法的详细介绍
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果线程在等待Semaphore信号量有可用的许可证的过程中,有其他线程调用当前线程的interrupt方法,中断请求,抛出中断异常
throw new InterruptedException();
}
} finally {
//如果线程等等待Semaphore信号量有可用许可证失败,即等待线程被中断
if (failed)
//将当前节点从同步队列中移除,可以看下面对cancelAcquire方法的介绍
cancelAcquire(node);
}
}
//Sync从AbstractQueuedSynchronizer继承下来的addWaiter方法
//@param mode 要创建节点的模式,是要等待Semaphore信号量有可用许可证节点
private Node addWaiter(Node mode) {
//根据当前线程和传入的节点模式创建新节点
Node node = new Node(Thread.currentThread(), mode);
//获取同步队列(CLH)的尾节点
Node pred = tail;
//如果尾节点不为空
if (pred != null) {
//将新建节点的前置节点设置为尾节点
node.prev = pred;
//使用CAS将新建节点设置为尾节点
if (compareAndSetTail(pred, node)) {
//如果CAS成功,尾节点的下一节点为新建节点
pred.next = node;
//返回新建节点
return node;
}
}
//否则调用enq方法进行循环的将新建节点加入同步队列中,做为同步队列的尾节点,详细的可以看下面enq方法的介绍
enq(node);
//返回新建节点
return node;
}
//Sync从AbstractQueuedSynchronizer继承下来的enq方法
private Node enq(final Node node) {
//死循环的将传入节点加入到同步队列中,做为同步队列的尾节点,直到节点加入队列成功为止
for (;;) {
//获取同步队列的尾节点
Node t = tail;
//如果尾节点为空,表明同步队列不存在节点
if (t == null) {
//新建个节点做为同步队列的头节点,使用CAS进行头节点的设置
if (compareAndSetHead(new Node()))
//如果头节点设置成功,将尾节点设置为头节点
tail = head;
} else {//否则队列不为空
//将新建节点的前置节点设置为尾节点
node.prev = t;
//使用CAS将新建节点设置为尾节点
if (compareAndSetTail(t, node)) {
//如果CAS成功,尾节点的下一节点为新建节点
t.next = node;
//返回新建节点
return t;
}
}
}
}
//AbstractQueuedSynchronizer中的内部类Node的predecessor方法
//获取当前节点的前驱节点,如果调用节点的前置节点为null,则抛出空指针异常
final Node predecessor() throws NullPointerException {
//当前节点的前驱节点
Node p = prev;
//如果前驱节点为空
if (p == null)
//抛出空指针异常
throw new NullPointerException();
else
//返回当前节点的前驱节点
return p;
}
//Sync从AbstractQueuedSynchronizer继承下来的setHeadAndPropagate方法,重新设置CLH队列头,如果CLH队列头的下一个节点为null或者头节点的下一节点模式为共享模式,那么就要唤醒同步队列的下一等待的线程
private void setHeadAndPropagate(Node node, int propagate) {
//获取同步队列的头节点
Node h = head;
//将传入进来的节点设置为同步队列的头节点,将传入进来的前置节点和线程都置为空
setHead(node);
//当propagate大于0,表示线程获取Semaphore信号量的propagate个许可证成功,或者头节点为空,或者头节点的状态为SIGNAL
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
// 获取新的CLH队列头的下一个节点s
Node s = node.next;
// 如果节点s是空或者共享模式节点,那么就要唤醒同步队列上等待的线程
if (s == null || s.isShared())
//唤醒下一个等待队列的线程,在下面对此方法进行介绍
doReleaseShared();
}
}
//Sync从AbstractQueuedSynchronizer继承下来的doReleaseShared方法,唤醒同步队列头节点的下一等待Semaphore信号量有可用的许可证的节点
private void doReleaseShared() {
for (;;) {
// 将同步队列头节点赋值给节点h
Node h = head;
// 如果节点h不为null,且不等于同步队列尾
if (h != null && h != tail) {
//得到节点h的状态
int ws = h.waitStatus;
//如果状态是Node.SIGNAL,就要唤醒节点h后继节点的线程
if (ws == Node.SIGNAL) {
// 将节点h的状态设置成0,如果设置失败,就继续循环,再试一次。
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒节点h后继节点的线程
unparkSuccessor(h);
}
//如果节点h的状态是0,就设置ws的状态是PROPAGATE,对AQS的内部类Node节点的状态不清楚的,可以看下我的另一篇对AQS源码的分析
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果同步队列头head节点发生改变,继续循环,
if (h == head)
// 如果同步队列头head节点没有改变,就跳出循环
break;
}
}
/**
* Sync从AbstractQueuedSynchronizer继承下来的shouldParkAfterFailedAcquire方法,根据前一个节点pred的状态,来判断当前节点对应的线程是否应该被阻塞
* @param pred : node节点的前一个节点
* @param node : 等待Semaphore信号量有可用的许可证的节点
* @return 返回true 表示当前线程应该被阻塞,然后调用parkAndCheckInterrupt方法来阻塞当前线程
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的状态值
int ws = pred.waitStatus;
//如果前置节点的状态值为SIGNAL
if (ws == Node.SIGNAL)
// 如果前一个pred的状态是Node.SIGNAL,那么直接返回true,当前线程应该被阻塞
return true;
//如果前置节点已经取消,循环获取不是取消的前置节点
if (ws > 0) {
// 如果前一个节点状态是Node.CANCELLED(大于0就是CANCELLED),
// 表示前一个节点所在线程已经被唤醒了,要从CLH队列中移除CANCELLED的节点。
// 所以从pred节点一直向前查找直到找到不是CANCELLED状态的节点,并把它赋值给node.prev,
// 表示node节点的前一个节点已经改变
do {
//重新赋值当前节点的前置节点
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//不是取消的前置节点的下一节点重新赋值为当前节点
pred.next = node;
} else {
// 此时前一个节点pred的状态只能是0或者PROPAGATE,不可能是CONDITION状态
// CONDITION(只在condition条件队列中节点存在,CLH同步队列中没有此状态的节点)
// 将前一个节点pred的状态设置成Node.SIGNAL,这样在下一次循环时,就是直接阻塞当前线程
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
//返回要等待Semaphore信号量有可用的许可证的节点对应的线程不会阻塞
return false;
}
//Sync从AbstractQueuedSynchronizer继承下来的parkAndCheckInterrupt方法,阻塞当前线程,并且唤醒时检验当前线程在等待的过程中是否有其他线程发起中断请求
private final boolean parkAndCheckInterrupt() {
//阻塞当前线程
LockSupport.park(this);
//当前线程被唤醒后,返回当前线程的中断标志位
return Thread.interrupted();
}
//将传入进来的节点从同步队列中移除,将传入节点对应的线程置为空,状态置为CANCELLED
private void cancelAcquire(Node node) {
//如果当前节点为空,直接退出
if (node == null)
//直接退出
return;
//将当前节点对应的线程置为空
node.thread = null;
//获取当前要取消节点的前置节点
Node pred = node.prev;
//循环跳过前置节点状态为CANNELLED的值
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//获取状态不是取消的前置的节点的下一个节点,在设置前置节点的下一个节点使用到
Node predNext = pred.next;
//将当前要取消的节点状态赋值为CANCELLED
node.waitStatus = Node.CANCELLED;
//如果要取消节点为尾节点,将尾节点设置为要取消节点的前一个节点
if (node == tail && compareAndSetTail(node, pred)) {
//如果设置成功,将要取消节点的前置节点的下一个节点设置为空
compareAndSetNext(pred, predNext, null);
} else {
int ws;
//如果前置不是头节点,并且前置节点的状态值为SIGNAL,或者将前置节点的状态值设置为SIGNAL,并且前置节点的线程不为空
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//获取要取消节点的下一个节点
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//否则的话唤醒node节点的下一个节点
unparkSuccessor(node);
}
//将要取消节点的下一节点设置为自身,加快gc
node.next = node;
}
}
/**
* 获取Semaphore信号量的1个许可证,直到获取Semaphore信号量的1个许可证成功,不支持中断,如果Semaphore信号量没有可用的许可证,线程会进入等待状态,直到Semaphore信号量有可用的许可证
*/
public void acquireUninterruptibly() {
//调用sync从AQS中继承下来的acquireShared方法获取Semaphore信号量的1个许可证
sync.acquireShared(1);
}
//Sync从AbstractQueuedSynchronizer中继承下来的acquireShared方法
public final void acquireShared(int arg) {
//调用Sync重写AQS的tryAcquireShared方法,获取Semaphore信号量的许可证,详细的可以看下NonfairSync和FairSync的tryAcquireShared方法
if (tryAcquireShared(arg) < 0)
//如果tryAcquireShared获取Semaphore信号量的许可证失败,调用doAcquireShared进行死循环获取Semaphore信号量的许可证,直到获取Semaphore信号量的许可证,可以看下面的对此方法的介绍
doAcquireShared(arg);
}
//Sync从AbstractQueuedSynchronizer中继承下来的doAcquireShared方法
private void doAcquireShared(int arg) {
//根据当前线程和共享模式创建新节点,addWaiter方法可以看上面的介绍
final Node node = addWaiter(Node.SHARED);
//在获取Semaphore信号量的许可证是否失败,如果失败在finally中将节点从同步队列中移除
boolean failed = true;
try {
//线程在等待Semaphore信号量有可用的许可证的过程中是否有其他线程发起中断请求,是否有中断请求的标志位
boolean interrupted = false;
for (;;) {
//获取新建节点的前置节点
final Node p = node.predecessor();
//新建节点的前置节点如果为头节点
if (p == head) {
//使用上面介绍的调用Sync重写AQS的tryAcquireShared方法获取Semaphore信号量的arg个许可证,如果r大于0表示获取arg个许可证成功
int r = tryAcquireShared(arg);
//如果r大于0,即获取arg个许可证成功
if (r >= 0) {
//设置当前节点为头节点,并且唤醒等待Semaphore信号量有可用许可证的下一个节点,可以看上面对setHeadAndPropagate方法的介绍
setHeadAndPropagate(node, r);
//将当前节点的前置节点下一个节点置为空,即当前节点的前置节点从同步队列中移除
p.next = null; // help GC
//如果当前线程在等待Semaphore信号量有arg个可用许可证过程中,有被其他线程发起中断请求
if (interrupted)
//重置中断请求标志位
selfInterrupt();
//线程获取arg个许可证成功
failed = false;
//直接返回
return;
}
}
//shouldParkAfterFailedAcquire方法在上一次获取semaphore信号量许可证失败时,是否需要等待,根据当前节点的前置节点状态来判断,详细的可以看上面的介绍
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //parkAndCheckInterrupt方法阻塞当前线程,并且在当前线程被唤醒时,检查当前线程是否被中断,parkAndCheckInterrupt返回中断标志位,详细的可以看上面的介绍
//parkAndCheckInterrupt返回线程在等待被唤醒时,线程是否被中断的标志位,如果线程被中断,将interrupted 标志位置为true
interrupted = true;
}
} finally {
//在获取Semaphore信号量的许可证过程失败
if (failed)
//如果获取Semaphore信号量的arg个许可证失败,从同步队列中移除当前节点,根据当前节点的前置节点状态是否唤醒当前节点的不为空的下一节点线程,cancelAcquire方法可以看上面详细介绍
cancelAcquire(node);
}
}
/**
* 尝试的获取Semaphore信号量的1个许可证,如果Semaphore信号量有可用的许可证,直接获取,如果没有直接返回,线程不会进入等待状态
*
* @return 获取Semaphore信号量的1个许可证是否成功,{code true}获取Semaphore信号量的1个许可证成功
*/
public boolean tryAcquire() {
//调用上面内部类Sync的nonfairTryAcquireShared方法非公平的获取Semaphore信号量的1个许可证,nonfairTryAcquireShared可以看上面的介绍
return sync.nonfairTryAcquireShared(1) >= 0;
}
/**
* 超时的获取Semaphore信号量的1个许可证,在超时时间内获取Semaphore信号量的1个许可证成功,或者超时Semaphore信号量还没有可用的许可证,返回获取Semaphore信号量的一个许可证失败,支持中断,如果Semaphore信号量没有可用的许可证,线程会进入等待状态,在超时时间内直到Semaphore信号量有可用的许可证,如果线程在等待Semaphore信号量的1个许可证的过程中被其他线程中断,抛出InterruptedException异常
*
* @return 返回在超时时间内获取Semaphore信号量的1个许可证是否成功,{code true}获取Semaphore信号量的1个许可证成功
*/
public boolean tryAcquire(long timeout, TimeUnit unit)
throws InterruptedException {
//调用sync从AQS中继承下来的tryAcquireSharedNanos方法超时的获取Semaphore信号量的1个许可证,tryAcquireSharedNanos方法可以看下面介绍
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//Sync从AbstractQueuedSynchronizer继承下来的tryAcquireSharedNanos方法
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
//检查线程是否有被中断
if (Thread.interrupted())
//有的话直接抛出中断异常
throw new InterruptedException();
//调用用上面介绍的调用Sync重写AQS的tryAcquireShared方法获取Semaphore信号量的arg个许可证
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout); //tryAcquireShared方法获取Semaphore信号量的arg个许可证失败,调用下面介绍的doAcquireSharedNanos方法在超时时间内循环的获取Semaphore信号量的arg个许可证
}
//AbstractQueuedSynchronizer的doAcquireSharedNanos,死循环的获取Semaphore信号量的arg个许可证,直到超时、或者获取到Semaphore信号量的arg个许可证成功
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
//如果传入的超时时间小于等于0
if (nanosTimeout <= 0L)
//返回获取Semaphore信号量的arg个许可证失败
return false;
//当前时间加上超时时间得到死亡时间
final long deadline = System.nanoTime() + nanosTimeout;
//根据当前线程和共享模式创建新节点,addWaiter方法可以看上面的介绍
final Node node = addWaiter(Node.SHARED);
//在获取Semaphore信号量的arg个许可证是否失败,如果失败在finally中将节点从同步队列中移除
boolean failed = true;
try {
//循环的获取Semaphore信号量的arg个许可证,直到超时、或者获取到Semaphore信号量的arg个许可证成功
for (;;) {
//获取新建节点的前置节点
final Node p = node.predecessor();
//新建节点的前置节点如果为头节点
if (p == head) {
//调用用上面介绍的调用Sync重写AQS的tryAcquireShared方法获取Semaphore信号量的arg个许可证,如果r大于0表示获取arg个许可证成功
int r = tryAcquireShared(arg);
//如果r大于0,即获取arg个许可证成功
if (r >= 0) {
//设置当前节点为头节点,并且唤醒等待Semaphore信号量有可用许可证的下一个节点,可以看下面对setHeadAndPropagate方法的介绍
setHeadAndPropagate(node, r);
//将当前节点的前置节点下一个节点置为空,即当前节点的前置节点从同步队列中移除
p.next = null; // help GC
//设置线程获取arg个许可证成功标志位
failed = false;
//返回线程获取arg个许可证成功
return true;
}
}
//死亡时间减去当前时间,得到超时时间
nanosTimeout = deadline - System.nanoTime();
//如果超时时间小于等于0,表明超时,直接返回线程获取arg个许可证成功失败
if (nanosTimeout <= 0L)
//返回线程获取arg个许可证失败
return false;
//shouldParkAfterFailedAcquire方法在上一次获取semaphore信号量许可证失败时,是否需要等待,根据当前节点的前置节点状态来判断,详细的可以看上面的介绍
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold) //超时时间大于一定的阈值,才会阻塞要获取Semaphore信号量的arg个许可证的线程
//超时等待要获取Semaphore信号量有arg个可用许可证的线程
LockSupport.parkNanos(this, nanosTimeout);
//在等待Semaphore信号量有arg个可用许可证时,有其他线程发起中断请求,直接抛出中断异常
if (Thread.interrupted())
//抛出中断异常
throw new InterruptedException();
}
} finally {
//在获取Semaphore信号量的许可证过程失败
if (failed)
//如果获取Semaphore信号量的arg个许可证失败,从同步队列中移除当前节点,根据当前节点的前置节点状态是否唤醒当前节点的不为空的下一节点线程,cancelAcquire方法可以看上面详细介绍
cancelAcquire(node);
}
}
/**
* 获取Semaphore信号量的permits个许可证,直到获取Semaphore信号量的permits个许可证成功,支持中断,如果Semaphore信号量没有permits个可用的许可证,线程会进入等待状态,直到Semaphore信号量有permits个可用的许可证,如果线程在等待Semaphore信号量的permits个许可证的过程中被其他线程中断,抛出InterruptedException异常
*
* @param permits 要获取Semaphore信号量的许可证的个数
*/
public void acquire(int permits) throws InterruptedException {
//如果传入的要获取许可证的个数是负数,抛出IllegalArgumentException异常,如果传入0也可以获取许可证成功,个人感觉这也是bug,如果一个资源只允许10个线程访问,如果调用此方法线程也会通过访问资源,有点违背许可证的概念
if (permits < 0) throw new IllegalArgumentException();
//调用sync从AQS中继承下来的acquireSharedInterruptibly方法获取Semaphore信号量的permits个许可证,详细的可以看上面对acquireSharedInterruptibly方法的介绍
sync.acquireSharedInterruptibly(permits);
}
/**
* 获取Semaphore信号量的permits个许可证,直到获取Semaphore信号量的permits个许可证成功,不支持中断,如果Semaphore信号量没有permits个可用的许可证,线程会进入等待状态,直到Semaphore信号量有permits个可用的许可证
*
* @param permits 要获取Semaphore信号量的许可证的个数
*/
public void acquireUninterruptibly(int permits) {
//如果传入的要获取许可证的个数是负数,抛出IllegalArgumentException异常
if (permits < 0) throw new IllegalArgumentException();
//调用sync从AQS中继承下来的acquireShared方法获取Semaphore信号量的permits个许可证
sync.acquireShared(permits);
}
/**
* 尝试的获取Semaphore信号量的permits个许可证,如果Semaphore信号量有permits个可用的许可证,直接获取,如果没有直接返回,线程不会进入等待状态
*
* @param permits 要获取Semaphore信号量的许可证的个数
* @return 获取Semaphore信号量的permits个许可证是否成功,{code true}获取Semaphore信号量的permits个许可证成功
*/
public boolean tryAcquire(int permits) {
//如果传入的要获取许可证的个数是负数,抛出IllegalArgumentException异常
if (permits < 0) throw new IllegalArgumentException();
//调用上面内部类Sync的nonfairTryAcquireShared方法非公平的获取Semaphore信号量的permits个许可证,nonfairTryAcquireShared可以看上面的介绍
return sync.nonfairTryAcquireShared(permits) >= 0;
}
/**
* 超时的获取Semaphore信号量的permits个许可证,在超时时间内获取Semaphore信号量的permits个许可证成功,或者超时Semaphore信号量还没有permits个可用的许可证,返回获取Semaphore信号量的permits个许可证失败,支持中断,如果Semaphore信号量没有permits个可用的许可证,线程会进入等待状态,在超时时间内直到Semaphore信号量有permits个可用的许可证,如果线程在等待Semaphore信号量的permits个许可证的过程中被其他线程中断,抛出InterruptedException异常
*
* @param permits 要获取Semaphore信号量的许可证的个数
* @return 返回在超时时间内获取Semaphore信号量的permits个许可证是否成功,{code true}获取Semaphore信号量的permits个许可证成功
*/
public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
throws InterruptedException {
//如果传入的要获取许可证的个数是负数,抛出IllegalArgumentException异常
if (permits < 0) throw new IllegalArgumentException();
//调用sync从AQS中继承下来的tryAcquireSharedNanos方法超时的获取Semaphore信号量的permits个许可证,tryAcquireSharedNanos方法可以看下面介绍
return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}
六、释放许可证
/**
* 释放Semaphore信号量的1个许可证,循环的释放,直到释放Semaphore信号量的1个许可证成功,感觉这也有个问题,如果没有获取许可证的线程,直接调用此方法,会导致增加Semaphore信号量的许可证
*/
public void release() {
//调用sync从AQS中继承下来的releaseShared方法释放Semaphore信号量的1个许可证,releaseShared方法可以看下面的介绍
sync.releaseShared(1);
}
//Sync从AbstractQueuedSynchronizer中继承下来的releaseShared方法
public final boolean releaseShared(int arg) {
//调用sync中的重写AQS的tryReleaseShared方法,释放Semaphore信号量的arg个许可证,如果释放成功,调用Sync从AQS继承下来的doReleaseShared方法
if (tryReleaseShared(arg)) {
//调用下面Sync从AbstractQueuedSynchronizer中继承下来的doReleaseShared方法
doReleaseShared();
//释放Semaphore信号量的arg个许可证成功
return true;
}
//释放Semaphore信号量的arg个许可证失败
return false;
}
//Sync从AbstractQueuedSynchronizer继承下来的doReleaseShared方法
private void doReleaseShared() {
//死循环修改头节点状态,根据头节点状态是否为SIGNAL状态值,唤醒头节点的下一个有效节点
for (;;) {
//获取头节点
Node h = head;
//头节点不为空,并且头尾节点不相同
if (h != null && h != tail) {
//获取头节点的状态值
int ws = h.waitStatus;
//头节点的状态值为Node.SIGNAL
if (ws == Node.SIGNAL) {
//将头节点的状态值从Node.SIGNAL修改为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
//如果失败重新循环
continue;
//唤醒头节点的下一有效
unparkSuccessor(h);
}
//如果头节点的状态值为0,将头节点的状态值从0修改为Node.PROPAGATE
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
//如果失败重新循环
continue;
}
//如果头节点没有改变,直接退出循环
if (h == head)
//退出循环
break;
}
}
/**
* 释放Semaphore信号量的permits个许可证,循环的释放,直到释放Semaphore信号量的permits个许可证成功,感觉这也有个问题,如果没有获取许可证的线程,直接调用此方法,会导致增加Semaphore信号量的许可证
*/
public void release(int permits) {
//如果传入的要释放许可证的个数是负数,抛出IllegalArgumentException异常
if (permits < 0) throw new IllegalArgumentException();
//调用sync从AQS中继承下来的releaseShared方法释放Semaphore信号量的permits个许可证,releaseShared方法可以看下面的介绍
sync.releaseShared(permits);
}
七、其他方法
//获取Semaphore信号量还有几个可用的许可证
public int availablePermits() {
//返回AQS的state属性值,即还剩余的可用许可证数,Sync的getPermits方法可以看上面的介绍
return sync.getPermits();
}
/**
* 获取Semaphore信号量所有的剩余许可证
*
* @param 返回获取到Semaphore信号量里剩余的几个许可证
*/
public int drainPermits() {
//调用上面介绍的内部类Sync的drainPermits方法进行Semaphore信号量所有的剩余许可证获取
return sync.drainPermits();
}
/**
* 减少Semaphore信号量的reduction个许可证,可能释放的许可证个数reductions大于Semaphore信号量还剩余的许可证个数,已经获取许可证的线程在释放许可证会抛出错误,或者新的线程要获取许可证,由于可用的许可证已经是负数,为此会抛出异常,感觉严谨的应该是需要将要减少的许可证个数和当前信号量剩余的许可证个数进行比较,如果大于返回错误或者不做任何操作
*
* @param reductions 要将Semaphore信号量的许可证个数减少reductions的许可证个数
*/
protected void reducePermits(int reduction) {
//如果传入要减少的reduction个许可证参数小于0,抛出IllegalArgumentException异常
if (reduction < 0) throw new IllegalArgumentException();
//调用上面介绍的内部类Sync的reducePermits方法进行减少Semaphore信号量的reduction个许可证
sync.reducePermits(reduction);
}
//判断Semaphore信号量实例在线程获取许可证是公平,还是非公平
public boolean isFair() {
//如果sync的实例是FairSync,则是公平的获取许可证
return sync instanceof FairSync;
}
//判断AQS中的同步队列是否有等待Semaphore信号量有可用许可证的节点
public final boolean hasQueuedThreads() {
//getSharedQueuedThreads方法可以看AQS中的源码分析,对此方法的介绍,也是从同步队列的尾节点开始往前遍历,获取模式是获取共享模式的节点的线程,因为Semaphore信号量是基于AQS的共享模式进行实现,加入到List集合中
return sync.hasQueuedThreads();
}
//获取AQS队列中有多少节点,返回是估计值,不是准确值,因为同步队列会改变,节点的加入和移除
public final int getQueueLength() {
//获取AQS队列中有多少节点的近似值,getQueueLength方法可以看AQS中的源码分析,对此方法的介绍
return sync.getQueueLength();
}
//获取AQS同步队列中的所有等待Semaphore信号量有可用许可证节点对应的不为空的线程
protected Collection<Thread> getQueuedThreads() {
//getQueuedThreads方法可以看AQS中的源码分析,对此方法的介绍
return sync.getQueuedThreads();
}
public String toString() {
return super.toString() + "[Permits = " + sync.getPermits() + "]";
}