前言
多线程,就是带着相同任务的程序,交给多个干活的同时干。
那么线程间能不能受我的控制去干活,不要一启动呼啦全执行完了。
比如:创建出来3个线程去吃饭睡觉,能不能不要在线程一启动就各干各的?能不能线程启动归启动,干啥归干啥的?
比如三个线程启动后,能不能听我口令再吃饭?能不能听我口令等这三兄弟都吃完了再去睡觉,能不能有点礼貌!?
CountDownLatch
理论实现
- 因为任务是一份,线程是多份。在任务节点上发出线程能感知的信号,即可控制每个来执行此任务的线程。
- 例如:统一吃饭:停住就要用countdownlatch的await方法。所有执行此任务的线程,走到这里都会停住。等待远方一声countdown命令才继续执行。
- 例如:大家都吃完:每个线程吃完饭都countdown想继续,在远方有个await收集都有谁想继续呀?收集够了所有人的,那就大家一起继续睡觉吧!
代码实现
@Slf4j
public class TestCountDownLatch implements Runnable {
//有多少干饭的兄弟?
private static final int HOW_MANY_BROTHER = 3;
//有多少老大管事呢?
private static final int HOW_MANY_BOSS = 1;
//等待同一时间开饭的停住口令,(由主线程下达开动命令)
private static CountDownLatch startEatingTogether = new CountDownLatch(HOW_MANY_BOSS);
//等待所有人都吃完,这口令创建时候就要定义好需要收集几次(由主线程下达开动命令)
private static CountDownLatch waitAllFinish= new CountDownLatch(HOW_MANY_BROTHER);
//所有线程都要做这件事,定义任务
@SneakyThrows
@Override
public void run() {
//停在这等口令
startEatingTogether.await();
//如果得到命令,继续向下执行,干饭3秒钟
log.info("兄弟--{}--号--干饭!",Thread.currentThread().getName());
Thread.sleep(3000);
//告诉老大干完饭,想继续,等老大收集齐大家的信号,就会继续向下执行
log.info("兄弟--{}--号--干完饭想睡觉!",Thread.currentThread().getName());
waitAllFinish.countDown();
//结束
}
//老大(主线程)来统一安排
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(HOW_MANY_BROTHER);
//来三个线程跑起来
for (int i = 0; i < 3; i++) {
executorService.execute(new TestCountDownLatch());
}
//程序执行到这里,即使上面三个线程都跑起来,但仍然卡在等口令那里
log.info("老大--{}--下达命令干饭!",Thread.currentThread().getName());
startEatingTogether.countDown();
waitAllFinish.await();
log.info("老大--{}--大家都吃完了!",Thread.currentThread().getName());
}
}
结果
源码解析(待补充)
同步控制倒数锁存器。使用AQS状态表示计数。
主角CountDownLatch长这样
await() 的原理
1-1 构造
//构造方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
//new Sync(count)如下
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
//state = 3
Sync(int count) {
setState(count);
}
//state = 0 才给放行
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
//每次来个countDown都减掉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;
}
}
}
1-2线程由此都去阻塞
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) {
//从这里返回的值此时都>0 因为status 给的3
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);
}
}
countDowm() 的原理
即将线程统一安排进阻塞等待队列里
1-1. state状态
遇到countDown,状态state = 1 改为 state = 0;
以下代码完成
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;
}
}
1-2. 循环唤醒阻塞等待队列里的next节点(线程)
private void doReleaseShared() {
for (;;) {
//取头节点
Node h = head;
if (h != null && h != tail) {
//把waitStatus = -1 改为 waitStatus = 0
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//此方法里取下个节点并唤醒,三个兄弟都会循环到这里被唤醒
unparkSuccessor(h);
}
//只有尾节点会走这里
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}