一、概述
semaphore
,字面意思是信号量,官方的如下:
* A counting semaphore. Conceptually, a semaphore maintains a set of
* permits. Each {
@link #acquire} blocks if necessary until a permit is
* available, and then takes it. Each {
@link #release} adds a permit,
* potentially releasing a blocking acquirer.
* However, no actual permit objects are used; the {
@code Semaphore} just
* keeps a count of the number available and acts accordingly.
*
* Semaphores are often used to restrict the number of threads than can
* access some (physical or logical) resource.
是不是感觉,这解释有点不接地气,又是信号量,又是许可啥的。
下面简单用一个场景来解释,一下子你就可以明白了。
比如说,某个银行大堂,只有三个办事窗口。
这时来了一堆人办业务,都被门口保安给拦住了。
保安说三个窗口,只让三个人进大堂。其它人只能在外边等着。
这时有个人办完业务,出来了。保安这时就放一个人进去。
总之,大堂里,最多只有三个人办理业务。
semaphore
的功能,和这个保安差不多,只让三个人进去。
其实 Semaphore 是 AQS 框架实现的,和 ReentrantLock 实现原理相似。
之前写过一篇《ReentrantLock 的源码分析》,如果看过,这篇就不用细看了。
二、示例代码
下面的代码,创建了 10 个线程,模拟 10 个线程抢 3 个许可的场景。
public static void main(String[] args) throws Exception {
Semaphore semaphore = new Semaphore(3,false); // 创建三个许可
CountDownLatch downLatch = new CountDownLatch(1);
for(int i = 0; i < 10; i++){
new Thread(() -> {
try {
downLatch.await();
semaphore.acquire(); // 获取许可
int randomSecond = RandomUtil.randomInt(1, 10);
Thread.sleep(randomSecond);
log.info("当前线程名:{},占用窗口时间:{},准备出来了。", Thread.currentThread().getName(),randomSecond);
semaphore.release(); // 解放许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
Thread.sleep(2000);
log.info("-------10个线程创建完毕,准备开始抢位置了……");
downLatch.countDown();
简单解释下代码
Semaphore semaphore = new Semaphore(3,false);
这句和意思,是创建3个许可,即上面例子中,保安说,只有三个窗口,只让三个人进去。
另: 这里参数 false,影响不大,是公平模式还是非公平模式,后面再具体说。
semaphore.acquire();
这句指使用一个许可。即上面例子中,某人占用了一个办事窗口。
如果执行这个方法时,许可用完了,即上面例子中,三个窗口都有人,那代码阻塞在这一行。
semaphore.release();
指许可使用完了,归还许可。即上面例子中,一个人办完业务,出来了。
执行这个方法时,会唤醒被阻塞的线程,使其尝试去申请许可。
前面说过 Semaphore 是 AQS 框架的,结合下面的图,说说 Semaphore的实现原理。
new Semaphore(3,false)
,即 state 设置为3,
semaphore.acquire();
有两种情况,
- state 大于 0,将 state 的值减 1,结束。(上面例子中,有空窗口,即有许可)
- state 小于等于0,进入队列中,等待被唤醒。(上面例子中,没有空窗口,即无许可)
semaphore.release();
将 state 值加1,并唤醒队列中第一个节点,让其去获取许可。
原理就这么点,也不复杂,是不是感觉,和 ReentrantLock 差不多呢?
确实差不多,都是有线程竞争时排队,都会限制访问某资源。
ReentrantLock 可以解决高并发问题,Semaphore 能解决高并发问题么?
不一定。new Semaphore(1,false),当把这个参数设置为 1时,跟 ReentrantLock 就一模一样了。
其它情况下,就不一定了。
比如说上面银行大堂的例子,保安是只让三个人进来办业务,
假如第一个窗口,银行前台妹子特别漂亮。
这三个人都抢着去第一个窗口,保安不干涉这个。
即,Semaphore 可以限制资源访问,但不保证原子性。
三、源码解析
源码部分是比较枯燥的,上面解释的差不多可以应对面试了。下面的源码解释可以不用看哈。
- 创建实例
Semaphore
类中有个内部类,Sync
,该类继承了 AQS
。
另外公平模式与非公平模式,各自是一个内部类,都继承了 Sync
。
abstract