Semaphore是AQS一个很重要的组件,之前对这块的认知有一点偏差,给自己埋了个坑,特意梳理一下。
1.Semaphore概念
Semaphore:即信号量,可以控制一组并发访问的线程的数目。核心方法acquire()和release(),acquire()是获取一个许可,如果没有足够的就等待,直到获取许可,release()是在操作完成后释放一个许可,如果没有release方法,到后面就会导致许可不够,其他的线程就一直无法得到许可,后面所有的线程就会阻塞在那里。下面是一个小demo,帮助理解
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3); //通道允许三个许可
for(int i = 0;i<20;i++){
final int threadNum = i;
exec.execute(()->{
try {
semaphore.acquire(); //获取一个许可,这里 可以更改参数同时获取多个许可,比如获取三个许可:semaphore.acquire(3);
...
semaphore.release(); //释放一个许可,同样的这里也可以设置释放的许可数
} catch (InterruptedException e) {
log.error("exception",e);
}
});
}
exec.shutdown();
Semaphored的构造方法还可设置第二个boolean类型的参数isFair , 如下:
final Semaphore semaphore = new Semaphore(3,true);
当boolean值为true时表示采取公平策略,false采取非公平策略,在公平策略中如果前面有线程在等待资源,该线程就会被放入FIFO等待队列,获取许可失败。但是非公平策略会抢占资源,即可能存在线程B获得了刚刚释放的许可,但是它前面还有线程A在等待。注意如果不设置,就默认采取非公平策略
2.tryAcquire()方法
如果业务并发量实在太大,为了控制并发需要舍弃部分线程,我们可通过tryAcquire()方法实现,下面是一个demo,只允许三个线程执行test()方法,这里同时有3个线程可以获得许可,但是每个获得许可的线程执行test()方法会sleep1秒,所以剩下的线程就获取不到许可,全部被放弃了
@Slf4j
public class SemaphoreDemo {
private static final int threadCount = 10;
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(3);
for(int i = 0;i<threadCount;i++){
final int threadNum = i;
exec.execute(()->{
try {
if(semaphore.tryAcquire()){ //尝试获取一个许可,如果获取到了许可,就继续执行,否则就丢弃掉。
test(threadNum);
semaphore.release(); //释放许可
}
} catch (InterruptedException e) {
log.error("exception",e);
}
});
}
exec.shutdown();
}
private static void test(int threadNum) throws InterruptedException {
log.info("{}", threadNum);
Thread.sleep(1000);
}
}
tryAcquire()默认尝试获取一个许可,如果需要获取多个,可设置参数,如下,尝试获取三个许可:
semaphore.tryAcquire(3);
tryAcquire()方法默认不等待,如果获取不到许可,就立即放弃当前线程,可通过设置时间参数设置等待时间,如下的demo表示等待一秒钟,如果一秒内获取不到许可就拉倒,直接放弃
semaphore.tryAcquire(1,TimeUnit.SECOND)//第一个参数传时间数值,第二个参数传时间单位,这里合起来表示1秒
当然你也可以同时设置等待时间和请求的许可数:
semaphore.tryAcquire(3,1,TimeUnit.SECOND)//结合了第二个和第三个,尝试获取三个许可,等1秒取不到就拉倒