CountDownLatch、CyclicBarrier和Semaphore说我常用的三种同步工具,其实是我们大部分人常学却不常用的。
本文默认大家都会AQS
CountDownLatch
官方解释:
一种同步辅助工具,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
CountDownLatch使用给定的计数进行初始化。wait方法会阻塞,直到当前计数由于countDown方法的调用而达到零,之后所有等待线程都会被释放,任何后续的wait调用都会立即返回。这是一种一次性现象——计数无法重置。如果您需要重置计数的版本,请考虑使用CyclicBarrier。
CountDownLatch是一种通用的同步工具,可用于多种目的。用计数1初始化的CountDownLatch用作简单的开/关锁存器或门:所有调用的线程都在门处等待,直到调用countDown的线程打开它。初始化为N的CountDownLatch可用于使一个线程等待,直到N个线程完成某个操作,或者某个操作已完成N次。
CountDownLatch的一个有用特性是,它不要求调用countDown的线程在继续之前等待计数达到零,它只是防止任何线程继续经过等待,直到所有线程都可以通过为止。
翻译的虽然比较生涩,但是意思都到了。
一、主要方法
1. await方法 => 重写的tryAcquireShared
protected int tryAcquireShared(int acquires) {
// 注意 !!当state = 0的时候才能够获取到锁,也就是才能执行
return (getState() == 0) ? 1 : -1;
}
2. countDown方法 => 重写的tryReleaseShared方法
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
// 当state减到0的时候,那么直接return false不会继续减少
if (c == 0)
return false;
// 注意!! release时 state是减1,正常在release中state是会加的
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
使用场景
1. 主线程等待多个工作线程完成,然后再继续执行
CountDownLatch countDownLatch = new CountDownLatch(2);
new Thread(()->{
try {
System.out.println("thread1......");
} finally {
countDownLatch.countDown();
}
}).start();
new Thread(()->{
try {
System.out.println("thread2......");
TimeUnit.SECONDS.sleep(2);
System.out.println("sleep end...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
countDownLatch.countDown();
}
}).start();
countDownLatch.await();
System.out.println("end");
2. 主线程控制多个工作线程一起执行
// 注意这里是1
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(()->{
try {
countDownLatch.await();
System.out.println("thread1......");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
countDownLatch.await();
System.out.println("thread2......");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
TimeUnit.SECONDS.sleep(1);
countDownLatch.countDown();
CyclicBarrier
官方解释:
一种同步辅助工具,允许一组线程全部等待对方到达一个共同的障碍点。CyclicBarrier在涉及固定大小的线程组的程序中很有用,这些线程组偶尔必须相互等待。屏障被称为循环的,因为它可以在等待线程释放后重新使用。
CyclicBarrier支持一个可选的Runnable命令,该命令在参与方中的最后一个线程到达之后,但在任何线程释放之前,每个屏障点运行一次。这种屏障操作有助于在任何一方继续之前更新共享状态。
一、主要方法
1. await方法
// count是可申请总量
// lock是锁
// trip是锁的条件
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
// 获取锁 下面的trip是lock的条件(trip = lock.newCondition())
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();
}
int index = --count;
// 当count为0时
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
if (command != null)
command.run();
ranAction = true;
nextGeneration();
return 0;
} finally {
if (!ranAction)
// 唤醒trip的条件队列
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();
}
}
二、使用场景
当多个线程都达到某一点再开始往下执行
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
new Thread(()->{
try {
cyclicBarrier.await();
System.out.println("thread1...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
cyclicBarrier.await();
System.out.println("thread2...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
}).start();
Semaphore
官方解释:
计数信号灯。从概念上讲,信号量维护一组许可。如有必要,每次收购都会阻止,直到有许可证可用,然后再获得许可证。每次发行都会增加一个许可证,可能会释放一个阻止收购的人。然而,没有使用实际的许可对象;Semaphore只是保持对可用数字的计数并相应地进行操作。
一、主要方法
1. acquire => tryAquireShared
// 公平锁的acquire
protected int tryAcquireShared(int acquires) {
for (;;) {
// 已经有等待队列,直接去排队
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 非公平锁 直接参与竞争,这里竞争也是和等待队列的头节点进行竞争!!!
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
2. release=>tryReleaseShared
没啥可说的
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
二、使用场景
主要⽤于那些资源有明确访问数量限制的场景,常⽤于限流。
额外: 取令牌一般取1比较合适,如果取多个,可能会发生剩余的共享资源足够某个等待的线程的需求,但是由于不是在等待队列的第一个不能被唤醒,不能执行的情况。
例子:
Semaphore semaphore = new Semaphore(6);
new Thread(()->{
try {
semaphore.acquire(3);
System.out.println("thread1...");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(3);
}
}).start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName());
semaphore.acquire(7);
System.out.println("thread2...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(4);
}
}).start();
TimeUnit.SECONDS.sleep(1);
// thread2给挡住了,即使总资源有6个,但是它永远无法执行。
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName());
semaphore.acquire(5);
System.out.println("thread3...");
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
semaphore.release(4);
}
}).start();