先写一个简单的demo
public class Test7766 {
static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+"-线程阻塞");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1");
t1.start();
try {
Thread.sleep(3000L);
} catch(Exception e){
e.printStackTrace();
}
countDownLatch.countDown();
System.out.println("主线程执行完毕");
}
}
先看到构造函数
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
首先判断入参不能小于0,否则直接抛IllegalArgumentException异常
接着再看到Sync构造做了啥
Sync(int count) {
setState(count);
}
设置了初始的状态。
代码跑起来,一开始主线程睡3秒,异步线程此时阻塞,等待主线程命令才会释放。
此时异步线程,先注释掉释放信号量的方法,看一下阻塞的过程。
此时代码跟到sync.acquireSharedInterruptibly(1);里面
发现此时getState()状态为1不满足0,所以最后返回-1。
因此
小于0条件满足,直接进入doAcquireSharedInterruptibly(arg);方法
再跟进去看
首先,同样是双向链表,生成一个当前线程的节点,往aqs链表后面继续添加
接着
获取到这个新节点的前驱节点,也就是head,所以p==head条件满足。
进入后就进行了一次重试,看看是否信号量被消费掉了。
因为此时我们没释放信号量,因此肯定还是阻塞,所以继续代码向下走
此时判断r>=0是否满足,但此时依然没有释放,故还是-1
这里原理就和ReentrantLock底层一样了。防止cpu空转,让其线程使用park()挂起,等待释放信号过来
此时debug断点打到之前一样的,park()的下面
找到countDown源码
在unparkSuccessor()方法里面打上断点
再次debug,等待3s,发现断点先走到了
然后按,F9跳掉,
代码直接回到
因此原理基本是一样的。基于LockSupport的等待与唤醒。
此时自旋就走到了
r>=0的里面
因为此时r已经等于0了。
最后返回。
此时就完成了一次流程。
首先看到tryReleaseShared()方法
底层同样是自旋的过程
一开始先判断如果c==0,说明已经没有阻塞的信号了,所以就直接返回false,但如果不等于0,说明还有阻塞,就通过cas,把0的结果覆盖掉旧的值1
最后返回nextc == 0
如果true,执行doReleaseShared();方法
此时可以看到
又是一个自旋过程。
先是判断ws是否是刚刚多次重试失败的,如果是,如果CAS将其唤醒。
此时CAS成功,唤醒等待的线程t1,最后代码
跳出循环。释放锁的过程也就随之结束。