2019独角兽企业重金招聘Python工程师标准>>>
类图
源码:
package java.util.concurrent;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
//在初始化时,初始化一个计数值.每执行countDown(),计数值减1;
//其他线程,可执行await()等待计数值为0才被唤醒继续执行
public class CountDownLatch {
private final Sync sync;
//继承AQS的内部类
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) {//返回状态值是否为0
return (getState() == 0) ? 1 : -1;
}
protected boolean tryReleaseShared(int releases) {//释放初始化的持有的锁资源
for (;;) {
int c = getState();
if (c == 0)//一开始初始化的锁资源,统统被释放了
return false;//返回false
int nextc = c-1;//计数值减1
if (compareAndSetState(c, nextc))//比较和更新计数值
return nextc == 0;//返回初始化持有的锁资源是否已被全部释放
}
}
}
//唯一的构造函数,初始化计数值
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//执行AQS的可中断共享锁的方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
//执行AQS在指定时间内获取可中断共享锁的方法
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
//执行tryReleaseShared(),若计数值为0.为0则什么也不做;不为0则计数值减1,将sync队列去除节点
public void countDown() {
sync.releaseShared(1);
}
//得到当前计数值
public long getCount() {
return sync.getCount();
}
//返回类名+'@'+hashcode+'[Count ='+计数值+']'
public String toString() {
return super.toString() + "[Count = " + sync.getCount() + "]";
}
}
类 CountDownLatch
extends Object
一个同步辅助类:某线程调用await() 之后将会一直等待,直到该组正在执行的线程的全部都执行完毕。
用给定的计数 初始化 CountDownLatch
。
由于调用了 countDown()
方法,所以在当前计数到达零之前,await
方法会一直受阻塞。
之后,会释放所有等待的线程,await
的所有后续调用都将立即返回。
计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier
。
构造方法摘要
CountDownLatch(int count) 构造一个用给定计数初始化的 CountDownLatch 。 |
方法摘要
void |
await() 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 |
boolean |
await(long timeout, TimeUnit unit) 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 |
void |
countDown() 递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 |
long |
getCount() 返回当前计数。 |
String |
toString() 返回标识此锁存器及其状态的字符串。 |
实现原理:
await():当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
AQS:acquireSharedInterruptibly(int arg)
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())//判断是否发生中断
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)//注意:-1表示获取到了共享锁,1表示没有获取共享锁
doAcquireSharedInterruptibly(arg);
}
CountDownLatch 内部类Sync:int tryAcquireShared(int acquires)
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;//这里的state就是最开始new CountDownLatch(int count),count等于state
}
如果获取共享锁继续调用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);//判断尝试获取锁
/*
这里要注意一下r的值就2种情况-1和1:
情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待
情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点
*/
if (r >= 0) {
setHeadAndPropagate(node, r);//将当前节点设置头结点。
p.next = null; // help GC 删除旧的头结点
failed = false;
return;
}
}
//当前节点不是头结点,当前线程一直等待,直到获取到共享锁。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如果这里多个线程wait之间没有调用countDown(),线程都在等待。如下图:
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // 记录头结点
setHead(node);//设置node为头结点
/*
*从上面传递过来 propagate = 1;
*一定会进入下面的判断
*/
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared
if (s == null || s.isShared())
doReleaseShared();//释放共享锁
}
}
isShared():
final boolean isShared() {
return nextWaiter == SHARED;
}
释放共享锁,通知后面的节点:
private void doReleaseShared() {
for (;;) {
Node h = head;//获得头结点
if (h != null && h != tail) {
int ws = h.waitStatus;//获取头结点的状态默认值为0
if (ws == Node.SIGNAL) {如果等于SIGNAL唤醒状态
//将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE
else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break
if (h == head) // loop if head changed
break;
}
}
compareAndSetWaitStatus(Node,int,int):
private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);//使用JNI调用c++代码
}
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);//如果当前计数值为0,则直接返回true
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)//参数时间小于等于0,直接返回false
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();//得到当前剩余时间
if (nanosTimeout <= 0L)//剩余时间小于等于0,则返回false
return false;
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)//判断是否应该挂起当前节点的线程
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())//恢复之后,立即判断是否是由于中断导致的唤醒,是则抛出异常。
throw new InterruptedException();
}
} finally {
if (failed)//抛出异常,则执行清理操作,将当前节点等待状态置为取消状态
cancelAcquire(node);
}
}
countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程
public void countDown() {
sync.releaseShared(1);//每次释放一个
}
boolean releaseShared(int arg):
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {//尝试释放共享锁
doReleaseShared();//释放共享锁
//由于state等于0,所以从队列中,从头结点一个接着一个退出(break),for循环等待,
return true;
}
return false;
}
tryReleaseShared(int):
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))//比较并设置state
return nextc == 0; //等于0的时候,肯定还有一些Node在队列中,所以要调用doReleaseShared(),来清理
}
}
CountDownLatch
public CountDownLatch(int count)
构造一个用给定计数初始化的 CountDownLatch
。
参数:
count
- 在线程能通过 await()
之前,必须调用 countDown()
的次数
抛出:
IllegalArgumentException
- 如果 count
为负
await
public void await() throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被 中断。
如果当前计数为零,则此方法立即返回。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下两种情况之一前,该线程将一直处于休眠状态:
- 由于调用
countDown()
方法,计数到达零;或者 - 其他某个线程中断当前线程。
如果当前线程:
- 在进入此方法时已经设置了该线程的中断状态;或者
- 在等待时被中断,
则抛出 InterruptedException
,并且清除当前线程的已中断状态。
抛出:
InterruptedException
- 如果当前线程在等待时被中断
await
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
使当前线程在锁存器倒计数至零之前一直等待,除非线程被 中断或超出了指定的等待时间。
如果当前计数为零,则此方法立刻返回 true
值。
如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:
- 由于调用
countDown()
方法,计数到达零;或者 - 其他某个线程中断当前线程;或者
- 已超出指定的等待时间。
如果计数到达零,则该方法返回 true
值。
如果当前线程:
- 在进入此方法时已经设置了该线程的中断状态;或者
- 在等待时被中断,
则抛出 InterruptedException
,并且清除当前线程的已中断状态。
如果超出了指定的等待时间,则返回值为 false
。如果该时间小于等于零,则此方法根本不会等待。
参数:
timeout
- 要等待的最长时间
unit
- timeout
参数的时间单位。
返回:
如果计数到达零,则返回 true
;如果在计数到达零之前超过了等待时间,则返回 false
抛出:
InterruptedException
- 如果当前线程在等待时被中断
countDown
public void countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。
如果当前计数等于零,则不发生任何操作。
getCount
public long getCount()
返回当前计数。
此方法通常用于调试和测试。
返回:
当前计数
toString
public String toString()
返回标识此锁存器及其状态的字符串。状态用括号括起来,包括字符串 "Count ="
,后跟当前计数。
覆盖:
类 Object
中的 toString
返回:
标识此锁存器及其状态的字符串
使用示例:
- 1.main线程只有CountDownLatch的计数器为0时,才会继续执行
package com.thread;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest1 extends Thread {
private static int LATCH_SIZE = 5;
private static CountDownLatch doneSignal;
public void run() {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 休眠片刻.");
//将CountDownLatch的数值减1
doneSignal.countDown();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 即将执行结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
doneSignal = new CountDownLatch(LATCH_SIZE);
// 新建5个任务
for (int i = 0; i < LATCH_SIZE; i++)
new CountDownLatchTest1().start();
System.out.println("main线程即将进入await");
// "主线程"等待线程池中5个任务的完成
doneSignal.await();
System.out.println("main线程继续执行.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
main线程即将进入await
Thread-2 休眠片刻.
Thread-1 休眠片刻.
Thread-4 休眠片刻.
Thread-0 休眠片刻.
Thread-3 休眠片刻.
main线程继续执行.
Thread-1 即将执行结束.
Thread-0 即将执行结束.
Thread-4 即将执行结束.
Thread-2 即将执行结束.
Thread-3 即将执行结束.
- 2.执行多次countDown()的运行结果:
package com.thread;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchTest1 extends Thread {
private static int LATCH_SIZE = 5;
private static CountDownLatch doneSignal;
public void run() {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + " 休眠片刻.");
//将CountDownLatch的数值减1
doneSignal.countDown();
doneSignal.countDown();
doneSignal.countDown();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 即将执行结束.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
try {
doneSignal = new CountDownLatch(LATCH_SIZE);
// 新建5个任务
for (int i = 0; i < LATCH_SIZE; i++)
new CountDownLatchTest1().start();
System.out.println("main线程即将进入await");
// "主线程"等待线程池中5个任务的完成
doneSignal.await();
System.out.println("main线程继续执行.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
main线程即将进入await
Thread-2 休眠片刻.
Thread-0 休眠片刻.
main线程继续执行.
Thread-3 休眠片刻.
Thread-4 休眠片刻.
Thread-1 休眠片刻.
Thread-0 即将执行结束.
Thread-2 即将执行结束.
Thread-3 即将执行结束.
Thread-4 即将执行结束.
Thread-1 即将执行结束.
多次执行 countDown() 可能会导致 mian线程提前唤醒。因为执行countDown()会直接将计数减1,导致部分线程还没执行countDown(),计数值就变成了0。当计数值已经变为0时,main线程就被提前唤醒,此时其他线程执行countDown(),根据源码可以发现,此时将什么也不做。