这些工具。。其实都是基于AQS的使用封装,其原理就不多讲了,AQS可以看下之前的文章。这里主要讲下几个方法和使用。
一、Semaphore
Semaphore 字面意思是信号量的意思,它的作用是控制访问特定资源的线程数目。可以简单的说是一个限流。就像停车场里的剩余车位,来一个少一个,车位满了就要等待。或者说是饭店里的餐位,一共一百个,满员后再来客人就要排队等待。也可以想成是连接池,拿走一个连接就少一个。
1、构造方法:
public Semaphore(int permits)
public Semaphore(int permits, boolean fair)
permits 表示许可线程的数量
fair 表示公平性,如果这个设为 true 的话,下次获取资源执行的线程会是等待最久的那个线程
2、常用方法:
acquire() 获取一个令牌,在获取到令牌、或者被其他线程调用中断之前线程一直处于阻塞状态。
acquire(int permits) 获取一个令牌,在获取到令牌、或者被其他线程调用中断、或超时之前线程一直处于阻塞状态。
acquireUninterruptibly() 获取一个令牌,在获取到令牌之前线程一直处于阻塞状态(忽略中断)。
tryAcquire()尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。
tryAcquire(long timeout, TimeUnit unit) 尝试获得令牌,在超时时间内自旋尝试获取,直到尝试获取成功或超时返回,不阻塞线程。
release()释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
hasQueuedThreads()等待队列里是否还存在等待线程。
getQueueLength()获取等待队列里阻塞的线程数。
drainPermits()清空令牌把可用令牌数置为0,返回清空令牌的数量。
availablePermits()返回可用的令牌数量。
使用举例:
图上获取了两个资源。线程中只有在前两个线程有资源释放的时候,其他线程才有机会获取。
实现原理:其实也是AQS的简单的运用(AQS前面已经详细说明)。
1、创建一个有两个资源的资源池,其实就是AQS的state设置为2。
2、获取一个资源,state-1. 当state < 0表示资源不足,这个时候会创建一个Node节点加入到CHL,然后挂起当前线程。
3、其他线程释放资源后,state+1,唤醒队列中的节点后,state-1.
二、CountDownLatch
来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。
场景:
1、某一线程在开始运行前等待n个线程执行完毕。将CountDownLatch的计数器初始化为new CountDownLatch(n),每当一个任务线程执行完毕,就将计数器减1 countdownLatch.countDown()(通常在finally语句里执行),当计数器的值变为0时,在CountDownLatch上await()的线程就会被唤醒。一个典型应用场景就是启动一个服务时,主线程需要等待多个组件加载完毕,之后再继续执行。(主线程等待其他线程执行完)
2、实现多个线程开始执行任务的最大并行性。注意是并行性,不是并发,强调的是多个线程在某一时刻同时开始执行。。做法是初始化一个共享的CountDownLatch(1),将其计算器初始化为1,多个线程在开始执行任务前首先countdownlatch.await(),当主线程调用countDown()时,计数器变为0,多个线程同时被唤醒。(主线程宣布其他线程同时执行)
主要方法:()
CountDownLatch.countDown() //计数器-1
CountDownLatch.await(); //计数器在归0之前,处于阻塞状态
实现原理(理解AQS的大概也能猜到步骤):
1、初始化CountDownLatch的时候将AQS的state值赋值为传进来 CountDownLatch的计数器的数量。
2、调用 countDown 将计数器-1. (若果减1后state为0,则唤醒CHL队列中的第一个线程。第一个线程则继续唤醒下一个线程)
3、调用 await 方法。查看state是否为0.如果为0,则唤醒CHL队列中的第一个线程。第一个线程则继续唤醒下一个线程),如果不为0,创建Node节点加入到CHL队列。
特点(有人说是缺点,看你怎么用它了):初始化后只能使用一次。就像田径比赛。所有人调用countDown方法后在那等着,裁判调用await方法后,所有人起头并。这个时候只能计数一次并没有 任何问题。只能说是看场景需要了。
三、CyclicBarrier
栅栏,也可以说是一个屏障,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续运行。上亿人都知道的场景:王者荣耀 5v5。等所有队友都确认到齐后才会开始一组新的游戏(所有参与者可以开始发挥自己的风骚操作了)。只要有一个人没到齐,都不能开始。(所有线程到达一个屏障点之后,才继续干各自的事情)
基于条件队列(条件队列下回说道)和lock实现的,时间是对count进行的计数操作。
主要方法:cyclicBarrier.await(); breakBarrier 等待方法和打破栅栏方法
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
if (g.broken)
throw new BrokenBarrierException();
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
//当调用await方法,将count方法-1.
int index = --count;
//当count等于0证明当前线程就是最后一个到达的现场,就不用再等待了
if (index == 0) { // tripped
boolean ranAction = false;
try {
//到这里证明人已到期,执行初始化时候的既定逻辑(barrierCommand 是初始化时指定到齐后要执行的逻辑)
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
//重新下一次 计数。这里也说明是可以重复使用的计数器
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
//跑到这证明就是。。。人还没到齐的
for (;;) {
try {
//分无限制等待和有时间到期的等待
if (!timed)
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
//被中断抛出异常。并且不等了
if (g == generation && ! g.broken) {
//停止等待的方法
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
//结束等待后发现自己被中断或者已经开启下一轮计数,抛出 异常、或者不等了抛出异常
if (g.broken)
throw new BrokenBarrierException();
if (g != generation)
return index;
if (timed && nanos <= 0L) {
//限时等待,而且超时了。不等了,直接唤醒
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
private void breakBarrier() {
//打破栅栏,或者说是本次计数结束
generation.broken = true;
//重置count,这里也是说明栅栏能复用的地方
count = parties;
//唤起所有等待线程开跑
trip.signalAll();
}
CountDownLatch 和 CyclicBarrier 的区别:(引用自网络总结)
1、CountDownLatch 的线程分为两类:一个是等待者,另一个是计数者;CyclicBarrier 参与的线程既是等待者,也是计数者。
2、CountDownLatch 一次性消费品,CountDownLatch 可以复用。
3、CountDownLatch 的计数值与线程没有必然联系,CyclicBarrier 的初始计数值与线程个数一致。
4、CountDownLatch 基于AQS实现,CyclicBarrier 基于ReentrantLock&Condition实现(再内部其实ConditionObject也是基于AQS啦)。