一、前瞻
并发编程源码解析(一)ReentrantLock源码解析(超详细)-CSDN博客
并发编程源码解析(二)ReentrantReadWriteLock源码解析之一写锁-CSDN博客
并发编程源码解析(三)ReentrantReadWriteLock源码解析之一写锁-CSDN博客
并发编程源码解析(四)ConcurrentHashMap源码解析之一基础概念介绍以及散列算法讲解-CSDN博客
并发编程源码解析(五)ConcurrentHashMap源码解析之二读与写源码分析-CSDN博客
并发编程源码解析(六)ConcurrentHashMap源码解析之三并发扩容
建议先看完前瞻的这些文章看完,至少也给看完 ReentrantLock 和 ReentrantReadWriteLock 因为在这些文章介绍的内容有可能就会在这边文章中省略。
二、介绍
CountDownLatch一般来说我们会称为计数器,是Java并发编程中的一个工具类,它可以帮助控制多个线程间的同步,使得某个线程在等待另外一组线程完成某些操作后再继续执行。
具体来说,CountDownLatch中包含一个初始值,其它线程可以通过调用这个对象的countDown()方法来减小这个值,而某个线程可以通过调用这个对象的await()方法来等待这个值减小到0。当这个值减小到0时,await()方法将返回,表示其它线程已经完成了某些操作,可以继续执行。
但是 CountDownLatch 只能使用一次,数字被减到0之后就不能再次使用了。
三、CountDownLatch的使用
以下是CountDownLatch的使用
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3); // 创建一个倒计时门闩,参数为计数器的初始值
Thread t1 = new Thread(() -> {
System.out.println("Thread 1 is running");
latch.countDown(); // 计数器减1
});
Thread t2 = new Thread(() -> {
System.out.println("Thread 2 is running");
latch.countDown(); // 计数器减1
});
Thread t3 = new Thread(() -> {
System.out.println("Thread 3 is running");
latch.countDown(); // 计数器减1
});
t1.start(); // 启动线程1
t2.start(); // 启动线程2
t3.start(); // 启动线程3
latch.await(); // 等待计数器归零,即等待所有线程执行完毕
System.out.println("All threads have finished"); // 所有线程执行完毕后输出此信息
}
}
四、 CountDownLatch 源码分析
4.1 构造器
这块逻辑比较简单只是把 state 设置为传入的 count。
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
//设置state的值
setState(count);
}
4.2 countDown()
countDown的逻辑主要就每次将 state 的次数减一即可,主要是调用释放共享锁的逻辑,这个逻辑在 ReentrantReadWriteLock 的读锁篇做过介绍,详细的就看接下的源码展示即可。
public void countDown() {
//释放共享锁
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
//已经释放完毕了,不能再释放了
if (c == 0)
return false;
int nextc = c-1;
//用CAS尝试修改state,如果修改成功则做一个判断
//是否state 已经为0了;如果为0则进入唤醒线程的流程
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//如果状态为-1唤醒下一个节点
if (ws == Node.SIGNAL) {
//如果失败重试
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒下一个节点
unparkSuccessor(h);
}
//这个在以后讲信号量的时候会提到,主要是修复了java-1.5的并发问题
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
4.3 await()
逻辑上和释放共享锁没有什么区别,所以完全可以参照 ReentrantReadWriteLock 里面的介绍内容,这里也会描述一下大概的逻辑。
public void await() throws InterruptedException {
//共享锁加锁
sync.acquireSharedInterruptibly(1);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//加入双向链表
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//从后往前遍历,原因请见ReentrantReadWriteLock读锁中有描述
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) &&
//这里会检查是否被打断,zhelshiwei的区别
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}