-
CountDownLatch
-
功能
1.需要等待多个线程都完成某个任务,然后唤醒主线程继续执行;
2.当两个线程时,主线程需要等待子线程完成任务到某个进度,然后唤醒主线程继续执行。 -
使用
1.创建CountDownLatch对象,给一个初始值n,可以理解为进度数字n
2.执行CountDownLatch的await方法,主线程会被阻塞,直到CountDownLatch设置的n等于0
3.在你认为需要调用CountDownLatch的countDown方法地方调用,这个时候其实n就会减一
4.如果你调用了countDown方法n次,那么主线程会从阻塞状态中被唤醒,主线程继续执行 -
原理
1.基于AQS实现的,利用AQS的同步功能
2.CountDownLatch的await方法其实就是将当前线程加入到阻塞队列,然后挂起当前线程
3.CountDownLatch的countDown方法其实就是尝试唤醒阻塞队列中的线程,当n=0的时候,主线程对应在阻 塞队 列的节点会被唤醒(n>0时会唤醒失败,但是每次都会尝试唤醒),唤醒后,主线程就继续执行。 -
代码
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 (;;) { final Node p = node.predecessor(); if (p == head) { //status值大于0,那么r会返回-1 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); } }
public void countDown() { sync.releaseShared(1); } public final boolean releaseShared(int arg) { //tryReleaseShared方法只有在status==0的情况下才会返回true,否则只对status-1, //然后返回false if (tryReleaseShared(arg)) { //doReleaseShared唤醒阻塞队列的线程,也就是头结点的next doReleaseShared(); return true; } return false; } private void doReleaseShared() { 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; } }
-
-
CyclicBarrier
-
功能
1.需要等待多个子线程都完成某个任务,最后一个子线程或者主线程唤醒所有子线程;
2.唤醒子线程们后,所有线程继续执行,或者也可以指定一个任务,在突破阻塞点的时候执行
3.可以重复使用,一次阻塞点被解除后可以继续使用 -
使用
1.创建CyclicBarrier对象,给一个初始值n,也就是多少线程在阻塞点后继续执行
2.在你的程序中调用CyclicBarrier的await方法,表示到达阻塞点,这个时候当前线程阻塞,这个方法既用于同步阻塞使用,也用于解除阻塞使用,当调用await是最后一个阻塞点的时候,会解除所谓的栅栏,这一点也是和CountDownLatch最大的区别,在实现方式上。 -
原理
1.基于AQS实现的,利用AQS的同步功能
2.CyclicBarrier的await方法会将当前线程加入到等待队列,然后挂起当前线程,等到最后一个await方法调用的时候,会唤醒(将条件队列转移到阻塞队列)所有条件队列的节点,最后唤醒阻塞队列的第一个节点开始执行。 -
代码
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException { final ReentrantLock lock = this.lock; lock.lock(); try { final Generation g = generation; if (g.broken) throw new BrokenBarrierException(); if (Thread.interrupted()) { breakBarrier(); throw new InterruptedException(); } //每次调用await方法都会将count减一 int index = --count; //如果是最后一个await被调用,那么执行下面的逻辑 if (index == 0) { // tripped boolean ranAction = false; try { //执行CyclicBarrier创建时指定的任务 final Runnable command = barrierCommand; if (command != null) command.run(); ranAction = true; //将所有等待队列的节点加入到阻塞队列中,重新初始化,可以重复使用目的 nextGeneration(); return 0; } finally { if (!ranAction) breakBarrier(); } } // loop until tripped, broken, interrupted, or timed out //如果不是最后一个await,那么count>0,执行下面逻辑 for (;;) { try { if (!timed) //将当前线程加入到等待队列中,当前线程挂起 trip.await(); else if (nanos > 0L) nanos = trip.awaitNanos(nanos); } catch (InterruptedException ie) { if (g == generation && ! g.broken) { breakBarrier(); throw ie; } else { // We're about to finish waiting even if we had not // been interrupted, so this interrupt is deemed to // "belong" to subsequent execution. Thread.currentThread().interrupt(); } } if (g.broken) throw new BrokenBarrierException(); if (g != generation) return index; if (timed && nanos <= 0L) { breakBarrier(); throw new TimeoutException(); } } } finally { //唤醒阻塞队列,让阻塞队列中任务开始执行 lock.unlock(); } }
-
-
总结
使用上:- CountDownLatch定义的阻塞点,也就是那个n,并不代表多少个线程,只是代表countDown的执行次数
- CyclicBarrier定义的阻塞点,也就是那个n,必须是要在n个线程中调用await方法,因为await会阻塞当前线程,所以调用多次也是徒劳,必须要n个线程调用才能消耗掉这个n
原理上
- CountDownLatch是直接基于阻塞队列的,一个CountDownLatch就是阻塞队列的一个节点,和n没关系
- CyclicBarrier是基于条件队列的,n是多少就有多少个节点体现在等待队列中,和n有关系。
(十二)JDK源码分析之常用并发工具类
最新推荐文章于 2022-01-11 19:29:21 发布