简介
通过这个类可以控制线程的执行顺序,比如有三个线程A、B、C,我需要在A、B两个线程执行完某个步骤之后再执行线程C的某些步骤,则可以通过CountDownLatch这个类进行控制,实现原理也是通过AQS来实现的。大家可以看看CountDownLatch这个类的源码,在源码的注释中给了一个很好的例子,后面我也会给出我本地测试的一个例子。
实现原理
- 核心的内部类Sync
- 继承了AbstractQueuedSynchronizer这个类。
(1)重写了tryAcquireShared和tryReleaseShared,非常简单的代码,但是就是这么简单的代码就实现了一个非常好的功能。// state等于0,返回1,表示可以获取锁,1这个值在后面还会用到 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } // 释放锁,每次释放锁就是直接将state的值减一 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; } }
- 三个核心方法
- countDown()
// 看到这就知道了countDown操作相当于于释放共享锁 public void countDown() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { // 这里进行state减一操作,如果减一后的结果是0,则返回true,表示锁没有被任何线程 //占用,则就可以进行doReleaseShared操作。 if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
相当于countDown每调用一次,state的值就减一,直到state等于zero时,才开始释放共享锁。
- await()
没有获取到锁,除非被中断,否则一直等着
public void await() throws InterruptedException {
// 获取锁,如果state大于0,这里调用会阻塞的
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 获取不到锁,返回-1
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
// 如果获取到锁后,会进行传播操作,主要是后面可能有线程共享锁
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
// 这里如果获取不到锁,就会调用LockSupport的park方法阻塞当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- await(long timeout, TimeUnit unit)
在规定的时间内还没有获取到锁,也直接返回
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
nanosTimeout = deadline - System.nanoTime();
// 如果超时了,直接返回false。
if (nanosTimeout <= 0L)
return false;
// 当前线程等待一段时间
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
从上面可以看到CountDownLatch这个类非常简单,核心的代码已经在AbstractQueuedSynchronizer这个类中写好了,所以AQS才是关键。
DEMO
import java.util.concurrent.CountDownLatch;
/**
* @Author: jiangcw
* @Date: 2019-9-22 下午 12:52
* @Version 1.0
*/
public class Test {
public static void main(String[] args) throws Exception {
CountDownLatch latch = new CountDownLatch(2);
new Thread(() -> {
System.out.println("我是老大,我先准备一下,准备好了我通知你们");
latch.countDown();
System.out.println("我是老大,多谢等我呀");
}).start();
new Thread(() -> {
System.out.println("我是老二,我先准备一下,准备好了我通知你们");
latch.countDown();
System.out.println("我是老二,多谢等我呀");
}).start();
new Thread(() -> {
System.out.println("我是老三,老大和老二你们准备好了记得通知我呀,然后我再问候你们");
try {
latch.await();
System.out.println("我是老三,我收到你们的通知了,我问候你呵呵呵");
}catch (InterruptedException e) {}
}).start();
}
}
如上所示,三个兄弟,老三需要等老大和老二准备好了之后,才能问候他两,所以等老大和老二都准备好后,调用countDown方法进行state减一操作,相当于一次通知,等state等于0后,表示老三收到两个兄弟的通知了,就可以问候他两了。
总结:
这个类的步骤如下:
(1)当调用new CountDownLatch(N)实例化时,会将state设置为N,表示当前锁已经被占有;
(2)当调用countDown时,如果state的值大于0,则将state进行减一操作,直到state的值等于0,当其值等于0后,等待的线程就会收到通知,实现获取锁操作。
(3)调用await方法时,如果state的值大于0,则会调用LockSupport的park之类的方法阻塞当前线程,直接state等于0后,其他线程主动调用LockSupport.unpark(Thread)方法,使线程从阻塞中恢复。
(4)对于有多个线程都调用await方法阻塞,是通过共享锁机制,调用setHeadAndPropagate这个方法让后继的线程从阻塞中恢复的。