CountDownLatch被大哥们形象化称为门栓,老规矩我对这个类的理解是这个类其实是一个倒计数器,举例说明一个使用场景
工厂老板收到了一个订单是生产一辆车,尊贵的老板肯定是不自己亲自干了,找来了三个工人,A工人负责生产轮胎,B工人负责生产门子,C工人负责生产门把手(这他么太简单了吧?)剧情需要一会可以给大伙讲这里面的细节
吸取昨天朋友们的建议写个小的示例代码
/**
* @author guozc
*
* 2020年6月30日
*/
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//三个倒计数的阻止器
CountDownLatch countDownLatch = new CountDownLatch(3);
//生产门子的开始干活
Thread makeMen = new Thread(new MakeMen(countDownLatch));
makeMen.start();
//生产轮子的开始干活
Thread makeLun = new Thread(new MakeLun(countDownLatch));
makeLun.start();
//生产把手的开始干活,不仅活少干的还晚
Thread makeBashou = new Thread(new MakeBashou(countDownLatch));
makeBashou.start();
//老板在此等着
countDownLatch.await();
System.out.println("组装结束");
}
}
门子生产线程
/**
* @author guozc
*
* 2020年6月30日
*/
public class MakeMen implements Runnable {
private CountDownLatch lock;
public MakeMen(CountDownLatch lock) {
this.lock = lock;
}
/**
* @author guozc
*
* 2020年6月30日
*/
@Override
public void run() {
try {
System.out.println("生产门子中");
//假设生产门子需要五秒
Thread.sleep(5000);
//为了方便看时间
System.out.println("门子组装完成"+ new Date());
lock.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
轮胎生产线程
/**
* @author guozc
*
* 2020年6月30日
*/
public class MakeLun implements Runnable {
private CountDownLatch lock;
public MakeLun(CountDownLatch lock) {
this.lock = lock;
}
/**
* @author guozc
*
* 2020年6月30日
*/
@Override
public void run() {
try {
System.out.println("生产轮子中");
//假设轮子生产需要五秒
Thread.sleep(5000);
//方便看时间
System.out.println("轮子组装完成"+ new Date());
lock.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
把手生产线程那个动身最晚,活最少的线程
/**
* @author guozc
*
* 2020年6月30日
*/
public class MakeBashou implements Runnable {
private CountDownLatch lock;
public MakeBashou(CountDownLatch lock) {
this.lock = lock;
}
/**
* @author guozc
*
* 2020年6月30日
*/
@Override
public void run() {
try {
System.out.println("生产把手中");
//一秒就生产完了
Thread.sleep(1000);
System.out.println("把手生产完成" + new Date());
lock.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
执行CountDownLatchTest main方法启动工作最后输出结果如下
生产门子中
生产轮子中
生产把手中
把手生产完成Tue Jun 30 19:26:13 CST 2020
轮子组装完成Tue Jun 30 19:26:17 CST 2020
门子组装完成Tue Jun 30 19:26:17 CST 2020
组装结束
可以看到把手很快就执行完了but组装并没有结束,所以CountDownLatch一个特点就是遵从木桶原理,整个任务的最终时间往往被时间最长的任务决定。
这里只是一个简单的小例子,实际中使用多数是要结合线程池进行调用,做到资源的保护避免疯狂创建过多分任务线程去执行,干趴系统资源
源码版本来自jdk1.8
下面简单撸一下源码,打开后映入眼帘的依然是那个熟悉的Sync类
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
##初始化任务数,或者说倒计数的数量,交给state变量保存
Sync(int count) {
setState(count);
}
##返回目前state的值,其实也是剩余的正在执行的任务数
int getCount() {
return getState();
}
##告诉给aqs,state是否是为0,如果不是那么当老板await的时候则让主线程park
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
##通过循环去减小state的值,子线程的countDown()方法使用
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;
}
}
}
下面是构造方法没啥可说的
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
通过初始化sync里面state数量进行倒计时数字初始化
下面是await方法,一般是在主线程里面使用
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
追一下 acquireSharedInterruptibly(1)代码来到了 AQS里面
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
再追一下 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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
主要作用检查竞争状态,如果竞争状态为0那么就把当前线程做对应的node设置为head并放行
否则就park,小马扎,小茶水伺候。等着通过realese时候后进行唤醒
await(long timeout, TimeUnit unit)为等待设置一个过期时间
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void countDown() 方法主要在执行任务线程中调用,当任务执行完减少倒计数
public void countDown() {
sync.releaseShared(1);
}
经过一番追踪最后会到执行到
CountDownLatch Sync类里面的
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;
}
}
逻辑很简单就是修改倒计数数字,如果倒计数数字为0了,那么就会执行AQS里面的
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
##竞争状态state为0了,也就是员工做做都干完了
##唤醒阻塞在队里面的node,其实就是刚才阻塞的老板那个主线程
doReleaseShared();
return true;
}
return false;
}
最后一个方法
public long getCount() {
return sync.getCount();
}
返回这一时刻的任务剩余量,还是那个问题不是很准确,因为在读的瞬间可能state值会被其他线程减小。注释也说的很清楚
this method is typically used for debugging and testing purposes
仅用作调试