CountDownLatch 计数器设置默认的 count ,然后调用 await() 的线程会一直被挂起,直到 count 个线程都执行 countDown() 之后,被挂起的线程才会唤醒,接着走下面的逻辑。
public static void main(String[] args) throws Exception{ CountDownLatch latch = new CountDownLatch(2); new Thread(()->{ try { Thread.sleep(2000); System.out.println("线程1准备执行 countDown 操作"); latch.countDown(); }catch (Exception e){ e.printStackTrace(); } }).start(); new Thread(()->{ try { Thread.sleep(2000); System.out.println("线程2准备执行 countDown 操作"); latch.countDown(); }catch (Exception e){ e.printStackTrace(); } }).start(); System.out.println("main() 线程准备执行 await"); latch.await(); System.out.println("success"); }
CountDownLatch
我们在创建 CountDownLatch 的时候,其实他就是一个 sycn 、继承了 aqs,可以猜到它的实现应该也是aps 那一套。首先就是将 count 赋值给了 state 标记锁状态。
await
主线程在 await() 的时候就等在了那里,直到所有线程执行完 countDown() , 主线程就继续往下执行。猜想他应该是进入了 aqs 的同步队列,直到其他线程将他唤醒。那我们就看看它的具体实现。
1. tryAcquireShared() 获取 state ,我们初始化的时候设置的是2,那么此时肯定 !=0 。
2. doAcquireSharedInterruptibly() 首先在 addWaiter() 里面for循环中循环两次将 当前线程加入等待队列中。
3. 然后获取到上一个节点 p, 它肯定是一个空的头节点。
4. 然后 tryAcquireShared() 再次判断一下锁占用状态。
5. setHeadAndPropagate() 将当前节点设置为头节点(因为可能不止一个地方吊桶 await() ,所以通过指针的变化一个个遍历出来)
6. 此时 r 肯定是>0 的,然后就 p.next=null 将自己从等待队中摘除出来
7. 最后在 parkAndCheckInterrupt() 里面调用 LockSupport.park() 将自己挂起,等待别人的唤醒。
countDown
1. tryReleaseShared() 先获取 state ,如果 !=0 说明有人持有锁,那就 cas 操作 -1 返回true。
2. doReleaseShared() 首先获取到头节点 h ,肯定不会为空的。
3. 然后如果状态是 Node.SIGNAL 那就 cas 改成 0 。
4. 最后在 unparkSuccessor() 里面获取头节点的下一个节点,也就是我们放在 同步队列 里面的main线程节点,通过 LockSupport.unpark() 唤醒它。
。