一种同步帮助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
一个
CountDownLatch
与给定数初始化。await
方法块直到当前计数达到零的countDown()
方法调用,之后,所有等待的线程,释放任何后续的调用await
立即返回。这是一一个镜头的现象-计数不能被重置。如果你需要一个版本,重置计数,考虑使用CyclicBarrier
。一个
CountDownLatch
是一种通用的同步工具,可以用于许多用途。一个CountDownLatch
初始化计算的一个作为一个简单的开/关锁,门:所有线程调用await
在门口等候直到它被一个线程调用countDown()
打开。一个CountDownLatch
初始化n可以用来使一个线程等待直到n个线程完成一些动作,或一些行动已经完成N次。一个
CountDownLatch
是一个有用的特性,它不需要线程countDown
等待计数达到零之前,它只是防止任何线程进行过await
直到所有的线程可以通过。-------------源自JDK1.8 API文档
那么其实CountDownLatch就是一个同步工具,让一个或多个线程进入等待,知道其他线程执行完毕后来唤醒这些等待的线程,采用的方式就是声明CountDownLatch给声明一个数量值,当这个数值变为0时,其他阻塞的线程被唤醒进入就绪状态,且这个工具的计数是不能重置的,如果需要的业务场景要用到重置的计数,可以选择使用CyclicBarrier
同步工具。
Modifier and Type | Method and Description |
---|---|
void |
await() 使当前线程等待直到锁向下计数为零,除非线程 interrupted。 |
boolean |
await(long timeout, TimeUnit unit) 使当前线程等待直到锁向下计数为零,除非线程 interrupted,或指定的等待时间的流逝。 |
void |
countDown() 减少锁的数量,释放所有等待的线程,如果计数为零。 |
long |
getCount() 返回当前计数。 |
String |
toString() 返回一个识别该锁存器的字符串,以及它的状态。 |
有几个需要注意的点:
CountDownLatch(int count)构造函数中的count是计数器的初始值,也就是说countDown()
方法至少被调用count次线程才会被唤醒,如果count<0那么会抛出异常
。
countDown()方法,调用一次count-1,如果count已经是0了,那么再调用此方法时什么都不做,此方法不会阻塞
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(2);
System.out.println(latch.getCount());
latch.countDown();
System.out.println(latch.getCount());
latch.countDown();
System.out.println(latch.getCount());
latch.countDown();
System.out.println(latch.getCount());
}
2
1
0
0
await()方法中两种情况会抛出异常:1:调用await()
前,线程被中断了 2:调用await()
后线程在等待期间被中断了
// 在调用`await()`方法前,当前线程的中断状态已经为 true 了
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
Thread.currentThread().interrupt();
cdl.await();
}
// 在等待的过程中被中断了
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
try {
cdl.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
Thread.sleep(500);
t1.interrupt();
}
await(long timeout, TimeUnit unit)
方法是有返回值的,为当前线程的阻塞设置一个等待时间,时间过后自动唤醒线程,若timeout值<=0那么不会进行线程阻塞;当计数器的值是0时,该方法返回值true
,否则返回false
一个简单的案例,主线程执行完毕后,子线程开始执行。
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(4);
new Thread(new CDLAble(latch)).start();
for (int i = 1; i < 5; i++) {
System.out.println("主进程执行第" + i + "遍");
latch.countDown();
}
}
}
@Data
class CDLAble implements Runnable {
CountDownLatch latch;
CDLAble(CountDownLatch latch) {
this.latch = latch;
}
@SneakyThrows
@Override
public void run() {
latch.await();
System.out.println("我是子进程,开始执行了");
}
}
假如涉及到主线程进行某时间后,子线程不管计数器是否已经为0都进行执行任务,那么可以使用await(long timeout, TimeUnit unit)
方法。
一个同步帮助,允许一组线程相互等待,以达到一个共同的障碍点。该障碍被称为循环,因为它可以在等待线程被释放后重新使用。
CyclicBarrier
可以理解为是一种循环栅栏,如学校运动会的百米冲刺,需要10个人先就位到达起跑点(可理解为栅栏),然后枪响开始起跑;而CyclicBarrier
就是一种循环栅栏,可以复用计数器,这点是与CountDownLatch不同的。
可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
public CyclicBarrier(int parties)
CyclicBarrier默认的构造方法是CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程使用await()方法告诉CyclicBarrier当前线程已经到达了屏障,然后当前线程被阻塞。
其中线程必须要调用await()
方法到达栅栏位置进行阻塞,并且如果parties小于1将会抛出异常。
public CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier的另一个构造函数CyclicBarrier(int parties, Runnable barrierAction),用于所有线程线程到达屏障时,最后到达的线程执行barrierAction,方便处理更复杂的业务场景。
贴一下源码,其实函数一就是调用的函数二
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
Modifier and Type | Method and Description |
---|---|
int |
await() 等待其他线程到达 await 这个屏障。 |
int |
await(long timeout, TimeUnit unit) 等到所有的 线程 await 这个障碍,或指定的等待时间。 |
int |
getNumberWaiting() 返回当前正在等待的障碍物的数量。 |
int |
getParties() 返回访问此障碍所需的线程数。 |
boolean |
isBroken() 查询,这个障碍是否是一个破碎的状态。 |
void |
reset() 重置为其初始状态的屏障。计数器重置 |
调用await方法的线程告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞。直到parties中所有参与线程调用了await方法,CyclicBarrier提供带超时时间的await和不带超时时间的await方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);//不超时等待
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
CyclicBarrier的核心方法就是这个dowait()
,底层用的ReentrantLock
来实现
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
// 获取锁
lock.lock();
try {
// 分代
final Generation g = generation;
// 当前generation“已损坏”,抛出BrokenBarrierException异常
// 抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrie
if (g.broken)
// 当某个线程试图等待处于断开状态的 barrier 时,或者 barrier 进入断开状态而线程处于等待状态时,抛出该异常
throw new BrokenBarrierException();
// 如果线程中断,终止CyclicBarrier
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 进来一个线程 count - 1
int index = --count;
// count == 0 表示所有线程均已到位,触发Runnable任务
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// 执行任务
if (command != null)
command.run();
ranAction = true;
//唤醒所有等待线程,并更新generation
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
// 如果不是超时等待,则调用Condition.await()方法等待
if (!timed)
trip.await();
else if (nanos > 0L)
// 超时等待,调用Condition.awaitNanos()方法等待
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();
// generation已经更新,返回index
if (g != generation)
return index;
// “超时等待”,并且时间已到,终止CyclicBarrier,并抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 解锁
lock.unlock();
}
}
所有的线程调用await()
进入阻塞状态,每有一个线程达到barrier屏障点时,计数器数量就-1,当数量到达0时,所有的线程将会被释放进入就绪状态,若barrier构造函数中有设置回调函数的任务,那么最后一个线程就会去执行这个回调函数来执行任务。
假如军训时,教官让同学们分为两队集合进行报到,一队先进行报到,5s后二队进行报到,并且由每队最后就位的同学汇报队伍集合完毕
public class CyclicBarrierTest {
public static void main(String[] args) throws InterruptedException {
int count = 6;
CyclicBarrier barrier = new CyclicBarrier(count, () -> System.out.println("报告,队伍集合完毕!"));
for (int i = 0; i < count; i++) {
// 开始进行报到
new Thread(new CBAble((i + 1) + "号", barrier)).start();
}
// 重置计数
barrier.reset();
Thread.sleep(5000);
for (int i = count; i < count*2; i++) {
// 开始进行报到
new Thread(new CBAble((i + 1) + "号", barrier)).start();
}
}
}
class CBAble implements Runnable {
private String name;
private CyclicBarrier barrier;
CBAble(String name, CyclicBarrier barrier) {
this.name = name;
this.barrier = barrier;
}
@SneakyThrows
@Override
public void run() {
System.out.println("报告,我是" + name + ",前来报到!");
barrier.await();
}
}
报告,我是1号,前来报到!
报告,我是6号,前来报到!
报告,我是5号,前来报到!
报告,我是4号,前来报到!
报告,我是3号,前来报到!
报告,我是2号,前来报到!
报告,队伍集合完毕!
报告,我是7号,前来报到!
报告,我是8号,前来报到!
报告,我是11号,前来报到!
报告,我是10号,前来报到!
报告,我是9号,前来报到!
报告,我是12号,前来报到!
报告,队伍集合完毕!
Semaphore
这个英文单词的意思是信号灯,即发送信号的那种灯,Java并发包中的Semaphore类是线程之间互相发送信号的工具。作用:限制某段代码块的并发数量
,表示某段代码只有n个线程可以访问,如果超出n,其他线程需要进行等待,等某个线程执行完这段代码后,下一个线程拿到许可证可以进入此代码块中执行。由此能看出,如果这段代码块中只允许一个线程访问,那么就无异于使用悲观锁了。
线程可以通过acquire()
方法获取到一个许可,然后对共享资源进行操作。注意如果许可已分配完了,那么线程将进入等待状态,直到其他线程释放许可才有机会再获取许可,线程释放一个许可通过release()
方法完成,"许可"
将被归还给Semaphore
。
Semaphore与ReentrantLock
一样基于AQS的子类Sync并有公平和非公平
模式。Sync继承自AQS,而NonfairSync
和FairSync
继承自Sync。
public Semaphore(int permits)
创建一个 Semaphore
与给定数量的许可证和nonfair公平设置,线程访问时采用非公平策略
public Semaphore(int permits, boolean fair)
创建一个 Semaphore
与给定数量的许可证,线程访问时的公平策略根据第二个参数的值确定。
Modifier and Type | Method and Description |
---|---|
void |
acquire() 从这个信号获取许可证,阻塞直到有一个可用的线程,或是 interrupted。 |
void |
acquire(int permits) 获得了从这个信号量许可证号码,阻塞直到所有的线程可用,或是 interrupted。 |
void |
acquireUninterruptibly() 从这个信号获取许可证,阻塞直到有可用的。 |
void |
acquireUninterruptibly(int permits) 获得了从这个信号量许可证号码,阻塞直到所有可用。 |
int |
availablePermits() 返回当前在该信号量可用许可证号码。 |
int |
drainPermits() 获取并返回立即可用的所有许可证。 |
protected Collection |
getQueuedThreads() 返回一个包含可能等待获取的线程的集合。 |
int |
getQueueLength() 返回等待获取的线程数的估计值。 |
boolean |
hasQueuedThreads() 查询是否有任何线程等待获取。 |
boolean |
isFair() 如果这个信号返回 true 公平设置为true。 |
protected void |
reducePermits(int reduction) 减少了可用的许可证的数量减少了。 |
void |
release() 发布许可证,返回到信号。 |
void |
release(int permits) 释放一定数量的许可证,让他们回归的信号。 |
String |
toString() 返回一个字符串识别这种信号,以及其状态。 |
boolean |
tryAcquire() 从这个信号获取许可证,只有一个人可以在调用时。 |
boolean |
tryAcquire(int permits) 获得了从这个信号量许可证号码,只有所有都可以在调用时。 |
boolean |
tryAcquire(int permits, long timeout, TimeUnit unit) 获得了从这个信号量许可证号码,如果所有可用在给定的等待时间和当前线程没有被 interrupted。 |
boolean |
tryAcquire(long timeout, TimeUnit unit) 从这个信号获取许可证,如果有一个可用在给定的等待时间和当前线程没有被 interrupted。 |
比较常用的就是acquire()
和release()
获取许可证和释放回许可证供其他线程进行访问代码块。其中有不少的方法都是直接调用的AQS类的具体实现(类内部的Sync继承自AQS类)
10个线程访问一段代码块,规定同时只有5个线程能访问到。
public class SemaphoreTest {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Thread(new STAble((i + 1), semaphore)).start();
}
}
}
class STAble implements Runnable {
private int name;
private Semaphore semaphore;
STAble(int name, Semaphore semaphore) {
this.name = name;
this.semaphore = semaphore;
}
@SneakyThrows
@Override
public void run() {
semaphore.acquire();
System.out.println(name + "获取资源");
//处理业务
Thread.sleep(1000);
System.out.println(name + "释放资源");
semaphore.release();
}
}
每次只有5个线程在执行任务,其他线程只能等待,等5个线程中有线程释放许可证后,其他线程才能有获取许可证执行任务的机会。
1获取资源
3获取资源
2获取资源
5获取资源
4获取资源
2释放资源
3释放资源
1释放资源
4释放资源
5释放资源
9获取资源
8获取资源
7获取资源
6获取资源
10获取资源
8释放资源
10释放资源
7释放资源
9释放资源
6释放资源