闭锁是一种线程同步工具,用于同步相关线程达到相关状态(并不一定要结束),然后再执行下一步操作,所以在所有的状态满足之前,调用await的线程将一直阻塞,相对于CyclicBarrier,闭锁更强调信号的同步。当然了,闭锁也能达到CyclicBarrier的效果。
假定有个任务,需要四个线程的任务都完成后才能继续执行,为了更便于理解,这里改为任务完成一半,任务类的逻辑如下:
static class Task implements Runnable {
private CountDownLatch latch;
public YieldTest(CountDownLatch latch) {
super();
this.latch = latch;
}
@Override
public void run() {
int times = 100;
while(times > 50) {
times --;
}
System.out.println(Thread.currentThread().getName() + " Half of Work!");
// 任务完成一半就通知其他线程
latch.countDown();
while(times > 0) {
times --;
}
System.out.println(Thread.currentThread().getName() + " Complete Work!");
}
}
调用者的代码示例如下:
final int count = 4;
CountDownLatch latch = new CountDownLatch(count);
long now = System.nanoTime();
for(int i = 0; i < count; i ++) {
new Thread(new YieldTest(latch)).start();;
}
// 将会一直阻塞,直到接收到所有的countDown通知
latch.await();
long end = System.nanoTime();
最后的输出结果如下,可见在任务完成一半后,闭锁就已经到达了结束状态,主线程可以继续运行:
Thread-0 Half of Work!
Thread-2 Half of Work!
Thread-3 Half of Work!
Thread-1 Half of Work!
Thread-3 Complete Work!
Thread-2 Complete Work!
Thread-0 Complete Work!
1670248
Thread-1 Complete Work!
闭锁、信号量都是基于AbstractQueuedSynchronizer进行实现,所以在到达结束状态之前,闭锁一直处于阻塞状态,每次计数时,就判断是否已到了终止状态,从而释放正在等待的线程,具体实现如下:
static class MyCountDownLatch extends AbstractQueuedSynchronizer {
public void countDown() {
this.releaseShared(1);
}
public void await() {
this.acquireShared(1);
}
/**
* @param counter
* 初始化可以同步的线程数量
*/
public MyCountDownLatch(int counter) {
super();
this.setState(counter);
}
/* (non-Javadoc)
* @see java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquireShared(int)
*/
@Override
protected int tryAcquireShared(int arg) {
return getState() == 0 ? 1 : -1;
}
/* (non-Javadoc)
* @see java.util.concurrent.locks.AbstractQueuedSynchronizer#tryReleaseShared(int)
*/
@Override
protected boolean tryReleaseShared(int arg) {
for(;;) {
int state = this.getState();
if(state == 0) {
return false;
}
int nextState = state - 1;
// 如果这次设置失败,则继续检测
// 不使用锁的另外一种解决办法
if(compareAndSetState(state, nextState)) {
return nextState == 0;
}
}
}
}
在上面的例子中,state状态变量与执行的操作(tryAcquireShared、tryReleaseShared)并没有直接的关系,只要满足方法返回结果的要求即可。
除此之外,上面还提供了一种不需要加锁而同步状态数据的方法,即充分利用状态变量的原子操作特性(主要是CAS操作),不停地进行自旋,从而达到检测与更新数据的目标。
结论
利用闭锁可以使一个或多个线程等待一组事件的发生,同时它还包含一个计数器,当计数器到达零时,表示所有需要等待的事情都已经发生。