信号量。
信号量是用来限制能访问共享资源的线程上限的。
共享资源有多个,允许访问共享资源,只是希望对线程的上限进行控制。
semaphore是停车场的车位,线程是汽车,剩余的空闲车位。
---261---
代码:
三个许可,用一个少一个。
package cn.itcast.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.Semaphore;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.TestSemaphore")
public class TestSemaphore {
public static void main(String[] args) {
// 1. 创建 semaphore 对象
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
semaphore.acquire();// 获得许可之后是可以被打断的
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
log.debug("running...");
sleep(1);
log.debug("end...");
} finally {
semaphore.release();// 释放许可
}
}).start();
}
}
}
---262---
应用:
限制的只是线程数。
资源数和线程数一样的时候用信号量是比较合适的。
改进数据库的连接池,数据库连接池是一个线程对应一个连接,不能多个线程对应一个数据库连接:
限流是限制连接数。
代码:
之前的代码:
代码:
第一步:在构造函数初始化一下让许可数和资源数是一样的。
第二步:借连接,没有许可就等待即可,相当于wait,进去取肯定会有的因为许可数和连接数是一样的。
第三步:归还许可
---263---
原理:
我们看下构造方法:
继承了同步器。
点进去父类的构造,实际上就是将state赋值给构造方法的state:
同步器继承aqs,构造方法的参数传给了states。
---------------------------------------------------------------------------------------------
构造方法就看完了。
看下线程的获取资源,是可打断的acquire:
点进去:
<0是失败,返回的是剩余的资源数。
点进去这个方法,找到子类:
点进去:
这里里面的逻辑很简答:
先获得当前的许可,减去我要用的,然后cas改为当前的许可-我要用的。
假设许可是0了,此时再进来一个,则为-1,此时就返回的是负数了,则再进入方法。
则进入到这个方法:
这个代码就是加入到等待队列里面:
分析这段代码:
1.首先创建一个节点,加入到队列。
就是把这两个节点都创建好了。
2.看下前驱节点是不是老二,老二就还有资格取竞争锁
3.前驱节点的状态改为-1,然后park住。
在这里改为-1的。
---264---
我们再看下release流程:
调用tryReleaseShare方法。
进入:
看下实现:
进入到这个方法:
此时state为0,说明都用完了。
0+1=1.
cas从0改为1。
不记录线程,只记录state。
---
再翻回来,进入这方法:
流程:
1.首先拿到头节点,看它是不是-1。-1的话就表示有后继的节点需要唤醒,尝试由-1改为0。
2.开始唤醒
3.我们看下阻塞的节点在哪里阻塞着呢
1.在箭头唤醒,看下前驱节点是不是头节点。结果是的,并且自己是老二。
2.再次:
这个把剩余量由1改为0。
3.再次进入if块
4.把自己设置为头节点,原来的节点就不要了。
唤醒后继的共享节点,由于共享节点是连续的,则挨个唤醒的。
但是受到states的限制了,还会进入park。
---265---