🏷️个人主页:牵着猫散步的鼠鼠
🏷️系列专栏:Java全栈-专栏
🏷️个人学习笔记,若有缺误,欢迎评论区指正
目录
1. 前言
2. 什么是Semaphore?
3. Semaphore源码解读
3.1 acquire():获取许可
3.2 release():释放许可
4. Semaphore的使用
5. 总结
1. 前言
在前面的文章我们讲解了CountDownLatch倒计时器的基本使用,本节我们继续来学习Java多线程编程中的一个工具类Semaphore信号量。
2. 什么是Semaphore?
Semaphore 是一个在多线程环境中用于控制对共享资源的访问的同步器(synchronizer),它是 Java 5 中引入的 java.util.concurrent(JUC)包的一部分。Semaphore 维护了一个许可集,线程在执行前必须从 Semaphore 获取一个许可。如果没有许可可用,线程将阻塞等待,直到其他线程释放许可。可以用来限制数据库连接数、限制服务器可处理请求数等。
3. Semaphore源码解读
我们跟进信号量的源码中浏览一圈,发现其实它内部主要的方法就2个:
// 初始共享资源数量
final Semaphore semaphore = new Semaphore(5);
// 获取1个许可
semaphore.acquire();
// 释放1个许可
semaphore.release();
3.1 acquire():获取许可
/*** 获取1个许可证*/
public void acquire() throws InterruptedException {sync.acquireSharedInterruptibly(1);
}
跟进这个方法后,我们会发现其内部调用了AQS的一个final 方法acquireSharedInterruptibly(),
/*** 共享模式下获取许可证,获取成功则返回,失败则加入阻塞队列,挂起线程*/
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {if (Thread.interrupted())throw new InterruptedException();// 尝试获取许可证,arg为获取许可证个数,当可用许可证数减当前获取的许可证数结果小于0,则创建一个节点加入阻塞队列,挂起当前线程。if (tryAcquireShared(arg) < 0)doAcquireSharedInterruptibly(arg);
}
我们可以发现这个方法中又调用了tryAcquireShared(arg)方法,作为AQS中的钩子方法,这个方法的实现在Semaphore的两个静态内部类 FairSync(公平模式) 和 NonfairSync(非公平模式) 中。虽然这个方法在AQS中,但它作为钩子方法,最终的实现则回到了Semaphore的内部类中。
我们接下来继续查看在 FairSync(公平模式)中的tryAcquireShared方法
static final class FairSync extends Sync {private static final long serialVersionUID = 2014338818796000944L;FairSync(int permits) {super(permits);}protected int tryAcquireShared(int acquires) {for (;;) {if (hasQueuedPredecessors())return -1;// 获取可用的许可数int available = getState();// 计算扣减后的许可数int remaining = available - acquires;if (remaining < 0 ||compareAndSetState(available, remaining))return remaining;}}}
这里就是获取剩余的许可数值,然后计算扣减后的许可数值,如果扣减后的值小于0,就会直接返回扣减数(||短路或,前者满足就不用判断后者了)。否认就会执行CAS操作compareAndSetState(available, remaining)扣减许可数的值。
这里我们就可知,Semaphore信号量的许可数是存放在AQS中的state变量中的(AQS源码我后续应该也会发文讲解),然后通过CAS来修改state的值,保证了操作的原子性。
3.2 release():释放许可
同样跟入这个方法,里面用了AQS的releaseShared(),而在这个方法内也毫无疑问的用了tryReleaseShared(int arg)这个钩子方法,原理同上,不再冗释,需要注意的是释放共享锁(也就是增加许可数state的值)的同时也会唤醒同步队列中的一个线程。
// 释放一个许可证
public void release() {sync.releaseShared(1);
}// 释放共享锁,同时会唤醒同步队列中的一个线程。
public final boolean releaseShared(int arg) {// 尝试释放共享锁(增加许可数)if (tryReleaseShared(arg)) {//唤醒同步队列中的一个线程doReleaseShared();return true;}return false;
}
protected final boolean tryReleaseShared(int releases) {for (;;) {int current = getState();int next = current + releases;// 溢出情况判断if (next < current) // overflowthrow new Error("Maximum permit count exceeded");if (compareAndSetState(current, next))return true;}}
4. Semaphore的使用
OK,讲到这里,把信号量中主要的方法解释完了,我们来写一个小demo感受一下它的使用:
public class Test {private final Semaphore semaphore;/*构造一个令牌*/public Test(int acq){this.semaphore= new Semaphore(acq);}public void useSemaphore(){try {semaphore.acquire();// 使用资源System.out.println("资源使用 " + Thread.currentThread().getName());Thread.sleep(1000); // 模拟资源使用时间} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();System.out.println("资源释放 " + Thread.currentThread().getName());}}public static void main(String[] args) {Test test = new Test(3);for (int i = 0; i < 5; i++) {new Thread(test::useSemaphore).start();}}
}
输出:
资源使用 Thread-0
资源使用 Thread-1
资源使用 Thread-3
资源释放 Thread-0
资源使用 Thread-2
资源使用 Thread-4
资源释放 Thread-1
资源释放 Thread-3
资源释放 Thread-4
资源释放 Thread-2
5. 总结
Semaphore是基于AQS和CAS操作实现的共享锁,利用AQS中被volatile修饰的变量state来代表许可证数(permit),当用户调用acquire()方法来获取许可证数时,他会计算出本次取出操作后的许可证数,如果小于0,那么就会挂起等待,如果大于0,也就是许可证数还够用,那么就会使用CAS操作修改许可证数。当调用release()方法释放许可证数时,会唤醒等待的线程去获取共享锁。
如果本文对你有帮助,点个小赞吧,后续有时间就多出出这方面的源码解读