长夜漫漫,无处消遣,外面还下着雨,好吧。。。。不如写一篇技术博客。
今天分享的技术博客主要是关于CountDownLatch的使用和源码分析。废话不多说,直接看下面:
CountDownLatch的概述
首先,CountDownLatch是一个工具类,它能够协调多个线程之间的同步。可以是一个线程等其他线程全部执行完毕之后再运行。
其内部实现原理很简单,主要是依靠一个计数器来完成的,技术器的初始值可以在定义CountDownLatch的对象时通过构造方法定义。每当一个线程执行完之后,计数器的值就会减1。当计数器的值为0时,表示所有的线程都已经执行完毕了。这个时候,阻塞等待的线程就可以继续运行了。
CountDownLatch在java中的使用
接下来,我定义三个子线程,我希望当这三个子线程里的任务全部执行完毕后,我的主线程才会继续执行,否则阻塞等待。
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("当前正在执行的线程名称为:" + Thread.currentThread().getName());
countDownLatch.countDown();
}, "thread-1").start();
new Thread(() -> {
System.out.println("当前正在执行的线程名称为:" + Thread.currentThread().getName());
countDownLatch.countDown();
}, "thread-2").start();
new Thread(() -> {
System.out.println("当前正在执行的线程名称为:" + Thread.currentThread().getName());
countDownLatch.countDown();
}, "thread-3").start();
//阻塞主线程,只有当3个子线程全部执行完毕时,才会唤醒
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前正在执行的是主线程,线程名称为:" + Thread.currentThread().getName());
}
}
执行后的结果如下:
当前正在执行的线程名称为:thread-1
当前正在执行的线程名称为:thread-2
当前正在执行的线程名称为:thread-3
当前正在执行的是主线程,线程名称为:main
源码分析
构造方法CountDownLatch(3)
**作用:**主要是为了初始化计数器statue
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
protected final void setState(int newState) {
state = newState;
}
countDown()方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//当所有线程执行完之后,返回true
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
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为0时才会返回true
return nextc == 0;
}
}
private void doReleaseShared() {
/*
* Ensure that a release propagates, even if there are other
* in-progress acquires/releases. This proceeds in the usual
* way of trying to unparkSuccessor of head if it needs
* signal. But if it does not, status is set to PROPAGATE to
* ensure that upon release, propagation continues.
* Additionally, we must loop in case a new node is added
* while we are doing this. Also, unlike other uses of
* unparkSuccessor, we need to know if CAS to reset status
* fails, if so rechecking.
*/
for (;;) {
Node h = head;
if (h != null && h != tail) {
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;
}
}
以下代码主要是从等待队列中找到离head节点最近的符合要求的节点进行唤醒
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
await()方法
- 第一次进入时,会查看计数器state。如果不为0,说明还需要继续等待,这个时候需要将当前线程放到AQS的等待队列中(关于等待队列的源码分析可以看"java并发编程之AQS")
- 自旋等待,一直等到state为0时,说明线程已经执行完毕,这个时候就需要唤醒等待队列中的线程。是唤醒head节点的下一个节点线程。在这上面的代码中,其实就是主线程本身。
- 线程唤醒之后,继续向下执行未执行完的代码。
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//如果小于0,说明暂时还有线程没有执行完
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
主要是获取计数器的值state,如果不为0,即暂时不能唤醒,返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//添加新线程节点入AQS同步队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//自旋,一直等到state为>=0结束循环
for (;;) {
//获取当前线程节点的前驱节点
final Node p = node.predecessor();
if (p == head) {
//获取当前state
int r = tryAcquireShared(arg);
//如果>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);
}
}
这里就是要将当前线程也就是阻塞等待的线程进入AQS等待序列中。关于AQS的源码分析可以参考文章java并发编程之AQS
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置当前线程节点为头节点,之前的头节点覆盖,等待gc回收
setHead(node);
/*
* Try to signal next queued node if:
* Propagation was indicated by caller,
* or was recorded (as h.waitStatus either before
* or after setHead) by a previous operation
* (note: this uses sign-check of waitStatus because
* PROPAGATE status may transition to SIGNAL.)
* and
* The next node is waiting in shared mode,
* or we don't know, because it appears null
*
* The conservatism in both of these checks may cause
* unnecessary wake-ups, but only when there are multiple
* racing acquires/releases, so most need signals now or soon
* anyway.
*/
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
当然await()可以定义阻塞超时时间,如果超过这个时间,其他线程还没有执行完,那么阻塞线程就不再阻塞。直接执行后面的内容。如下代码:
public class CountDownLatchDemo {
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("当前正在执行的线程名称为:" + Thread.currentThread().getName());
countDownLatch.countDown();
}, "thread-1").start();
new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前正在执行的线程名称为:" + Thread.currentThread().getName());
countDownLatch.countDown();
}, "thread-2").start();
new Thread(() -> {
System.out.println("当前正在执行的线程名称为:" + Thread.currentThread().getName());
countDownLatch.countDown();
}, "thread-3").start();
//阻塞主线程,只有当3个子线程全部执行完毕时,才会唤醒
try {
countDownLatch.await(1,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前正在执行的是主线程,线程名称为:" + Thread.currentThread().getName());
}
}
执行结果:
当前正在执行的线程名称为:thread-1
当前正在执行的线程名称为:thread-3
当前正在执行的是主线程,线程名称为:main
当前正在执行的线程名称为:thread-2
Process finished with exit code 0
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();
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);
}
}