闭锁
闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。闭锁的作用相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,并且没有任何线程能够通过,当到达结束状态时,这扇门会打来并允许所有的线程通过。当闭锁到达结束状态后,将不会再改变状态,因此这扇门将永远保持打开状态。闭锁可以用来确保某些活动直到其他活动都完成后才继续执行,例如:
- 确保某个计算在其需要的所有资源都被初始化之后才继续执行。
- 确保某个服务在其依赖的所有其他服务都已经启动之后才启动。
- 等到直到直到某个操作的所有参与者(例如,在多玩家游戏中的所有玩家)都就绪再继续执行。
CountDownLatch是一种灵活的闭锁实现。闭锁状态包括一个计数器,该计数器被初始化为一个正数,表示需要等待的事件数量。countDown方法递减计数器,表示有一个事件已经发生了,而await方法等待计数器达到零,这表示所有需要等待的事件都已发生。如果计数器的值非零,那么await方法会一直阻塞直到计算器为零,或者等待中的线程中断,或者超时。
下面是CountDownLatch的一个简单例子:运行代码看效果更容易理解
package com.joonwhee.imp;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* CountDownLatch的简单例子
* @author JoonWhee
* @Date 2018年1月27日
*/
public class CountDownLatchTest {
static CountDownLatch timeOutCountDownLatch = new CountDownLatch(1);
public static void main(String args[]) {
try {
new Driver(10);
testAwaitTimtOut();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 测试带超时的await方法
public static void testAwaitTimtOut() throws InterruptedException {
System.out.println("before await(long timeout, TimeUnit unit)");
timeOutCountDownLatch.await(3, TimeUnit.SECONDS); //等待超时时间为3秒
System.out.println("after await(long timeout, TimeUnit unit)");
}
}
class Driver {
public Driver(int N) throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1); // 定义一个CountDownLatch, 计数器值为1, 也就是每次await(), 需要执行1次countDown(), 才能继续执行await()外面的代码
CountDownLatch doneSignal = new CountDownLatch(N); // 定义一个CountDownLatch, 计数器值为N, 也就是每次await(), 需要执行N次countDown(), 才能继续执行await()外面的代码
for (int i = 0; i < N; ++i) {
// 创建并启动线程
new Thread(new Worker(startSignal, doneSignal)).start();
}
Thread.sleep(2000); // 睡眠2秒, 可以看到10个线程都在等待startSignal.countDown()执行
System.out.println();
startSignal.countDown(); // 解除所有线程的阻塞
doneSignal.await(); // 等待所有线程执行doneSignal.countDown(), 才通过
System.out.println();
System.out.println("Main thread-after:doneSignal.await()------");
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "-before:startSignal.await()");
startSignal.await(); // 线程会在此处等待, 直到startSignal.countDown()执行
System.out.println(Thread.currentThread().getName() + "-after:startSignal.await()");
doneSignal.countDown();
} catch (InterruptedException ex) {
} // return;
}
}
注意:
CountDownLatch同步机制Sync也是基于AQS的,但没有公平和非公平模式之分;
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
常用方法
await()
/** Causes the current thread to wait until the latch has counted down to
* zero, unless the thread is {@linkplain Thread#interrupt interrupted}.
*
* <p>If the current count is zero then this method returns immediately.
*
* <p>If the current count is greater than zero then the current
* thread becomes disabled for thread scheduling purposes and lies
* dormant until one of two things happen:
* <ul>
* <li>The count reaches zero due to invocations of the
* {@link #countDown} method; or
* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread.
* </ul>
**/
public void await() throws InterruptedException {}
countDown()
/**
* Decrements the count of the latch, releasing all waiting threads if
* the count reaches zero.
*
* <p>If the current count is greater than zero then it is decremented.
* If the new count is zero then all waiting threads are re-enabled for
* thread scheduling purposes.
*
* <p>If the current count equals zero then nothing happens.
*/
public void countDown() {}