Java 并发 --- CountDownLatch源码分析
来源:互联网 发布:手机淘宝如何举报店铺 编辑:程序博客网 时间:2024/06/08 03:55
CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完(也可以说到达一个指定的点,并不是指线程运行结束)后再执行,本文分析的是jdk 1.8 中的源码。
实现某个线程等待其余线程完成任务的最简单做法是使用join()方法(join 用于当前执行线程等待被join 线程执行结束),其实现原理就是不停的检查被join 线程是否存活,如果被join 线程存活,则继续让当前线程等待。
CountDownLatch 也可以实现这样的功能,今天我们来看看CountDownLatch 是如何实现这类似的功能的。
使用
import java.util.concurrent.CountDownLatch;class CountDownLatchThread extends Thread { private CountDownLatch countDownLatch; public CountDownLatchThread(String name, CountDownLatch countDownLatch) { super(name); this.countDownLatch = countDownLatch; } public void run() { System.out.println(Thread.currentThread().getName() + " start doing something"); try { // 休眠2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " finish"); //任务结束,调用 countDown 方法 countDownLatch.countDown(); }}public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatch countDownLatch = new CountDownLatch(2); CountDownLatchThread c1 = new CountDownLatchThread("c1", countDownLatch); CountDownLatchThread c2 = new CountDownLatchThread("c2", countDownLatch); //c1 开始 c1.start(); //c2 开始 c2.start(); System.out.println("Waiting for c1 thread and c2 thread to finish"); try { // main 线程等待c1,c2 结束 countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " continue"); }}
CountDownLatch 的使用非常简单,通过构造函数指定需要等待的线程数量,等待者调用CountDownLatch 的await() 方法,被等待者调用CountDownLatch 的countDown 方法(都是针对的同一个CountDownLatch 实例哟)。
知道了如何使用,接下来我们看看CountDownLatch 是如何完成这 “等待–通知“机制。
数据结构
public class CountDownLatch { //继承同步器 private static final class Sync extends AbstractQueuedSynchronizer { Sync(int count) { setState(count); } int getCount() { return getState(); } //重写同步器中的模板方法 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } //重写同步器中的模板方法 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)) return nextc == 0; } } } private final Sync sync; ... }
CountDownLatch,其底层是通过队列同步器(AQS)来实现的,一起来回顾一下AQS:
队列同步器(AQS)是一种用于线程同步的一个组件,其内部通过cas操作来实现原子操作,AQS 有两种队列和两种模式
两种队列:
同步队列和条件队列,不同的条件会有不同的条件队列。
两种模式:
独占模式和共享模式,独占模式 也就是一个线程在使用的时候,其它线程只能等待,共享模式就是可以设置多个线程同时访问。
同步状态:
AQS 中维护着一个同步状态(实际就是一个整型变量),独占模式下只有两种状态(占有和空闲),共享模式下可以表示为允许多少个线程同时访问。
在独占模式中:如果线程能获取到同步状态(设置同步状态为占有状态),那么就相对于获取到了锁,其它线程再获取同步状态的时候就会失败,这个时候其它线程会进行一定时间的自旋,如果期间仍然没有获取到同步状态,那么就只能放到同步队列中,让其线程阻塞等待。当获取到同步状态的线程执行完后,释放同步状态,同时唤醒同步状态队头的等待线程。
在共享模式中:其实和独占模式大同小异,设置同步状态为一个数值,表示最多允许好多个线程同时访问,当一个线程获取到同步状态后,就将同步状态递减,后续线程也是如此,如果当一个线程来获取同步状态时,发现无法获取了,那么就进行自旋,然后再放到同步队列中,只是这个时候节点的是共享模式,而不是上面的独占模式,独占模式某个节点被唤醒之后,它只需要将这个节点设置成head就完事了,而共享模式不一样,某个节点被设置为head之后,还可能需要唤醒后面的节点, 如果它的后继节点是SHARED状态的,那么将继续通过doReleaseShared方法尝试往后唤醒节点,实现了共享状态的向后传播。
AQS这里就简单回顾了一下,想要了解更多可以参考我前面写的内容:
Java 并发 —AbstractQueuedSynchronizer(同步器)-独占模式
Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition
构造方法
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
CountDownLatch 在实例化的时候需要指定count,而这个count的数值就作为了同步器中的同步状态值。
Sync(int count) { //调用同步器中方法,设置同步器状态的值 setState(count); }
await 方法
该方法会使当前线程在同步状态计数至零之前一直等待,除非线程被中断。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
这个acquireSharedInterruptibly 是同步器中的方法,我们在来看一遍:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg);}
如果发生中断,则抛出中断异常,否则调用tryAcquireShared,如果返回小于0,则后面就是自旋和加入阻塞队列操作了,这个里面是同步器里面的内容,tryAcquireShared 是我们需要重写的方法,通过我们来决定再不满足何条件时才放入到同步队列,来看看CountDownLatch 中Sync 的tryAcquireShared 方法。
protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1;}
这个很简单,如果同步状态为0,则返回1,否则返回负数,这样也就是说,如果同步状态不为0,那么就可能会把请求await()方法的线程加入到同步队列(这里用的是可能,因此在同步队列器中,在加入到同步队列之前,会再次尝试获取状态(执行tryAcquireShared),因此这个时候可能会满足,而不加入到同步队列中)。
CountDownLatch调用await 的含义是什么:就是等待其它线程完成操作,而其它线程完成操作会设置同步状态的值,这样如果被等待的线程都完成了,那么同步状态的值就为0了,也就完成了等待其它线程完成操作这一任务了。
除此之外,await 还有两一个超时方法:
await(long timeout, TimeUnit unit) 方法
public boolean await(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); }
这个其实就是调用的同步器中的超时方法,如果在一定时间内未获取到同步状态(执行tryAcquireShared 成功),那么就返回false,而不是一直阻塞等待。
前面说了,被等待的线程可以更新同步状态,那么其它线程如何更新同步状态呢,这个我们继续往下看。
countDown 方法
public void countDown() { sync.releaseShared(1); // 释放同步状态 }
releaseShared也是同步器中的方法:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
同样会调用我们重写的tryReleaseShared方法,如果该方法返回true,那么就会唤醒同步队列中共享模式的的线程。
protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero for (;;) { int c = getState(); //得到同步状态 if (c == 0) return false; // 如果同步状态已经为0,返回false int nextc = c-1; //递减 if (compareAndSetState(c, nextc)) //cas 设置同步状态 return nextc == 0; //如果同步状态为0了,则需要承担唤醒任务 }}
当被等待的线程 完成任务,调用countDown 方法时,会将同步状态递减,如果同步状态还未到0,那么说明还有其他线程获取了同步状态,那么就不用进行唤醒工作,如果更新后同步状态为0了,说明所有线程都完成任务了,需要唤醒等待者了。如果调用该方法同步状态已经为0了,那么严格来说是有问题的,同步状态已经不需要释放了,返回false.
在CountDownLatch 中可以等待多个线程,被等待的线程 相当于持有同步状态,当自身完成任务时,则释放同步状态, 等待者在同步状态被释放完之前,一直等待,当同步状态释放完后,会唤醒等待者,通过上面利用队列同步器,通过简单的设置同步器状态就能完成这一操作。同步状态不是简单的占有和非占有两种状态,因此CountDownLatch 是一种共享模式。这一点现在通过我们的分享就可以得出,不仅仅只是通过其源代码得出。
总结
经过分析CountDownLatch的源码可知,其底层结构是AQS,对其线程所封装的结点是采用共享模式,AQS 使用的是模板设计方法,只需要我们重写几个简单的方法,就可以实现我们自己的同步组件了。
Java 并发 —AbstractQueuedSynchronizer(同步器)-独占模式
Java 并发 —AbstractQueuedSynchronizer-共享模式与Condition
- Java 并发 --- CountDownLatch源码分析
- Java并发之CountDownLatch源码分析
- 《Java源码分析》:CountDownLatch
- 《Java源码分析》:CountDownLatch
- 【Java8源码分析】并发包-CountDownLatch
- Java源码分析之CountDownLatch
- 【JAVA-并发】CountDownLatch用法和源码浅析
- Java并发之CountDownLatch的使用和源码解析
- Java并发同步器--CountDownLatch
- Java并发之CountDownLatch
- Java并发之CountDownLatch
- Java并发编程:CountDownLatch
- java并发学习----CountDownLatch
- Java并发编程---CountDownLatch
- 【java并发】CountDownLatch
- JAVA 并发编程CountDownLatch
- JUC - CountDownLatch 源码分析
- 源码分析-CountDownLatch
- Shiro
- PAT 乙级 1007. 素数对猜想 (20) c++ 筛选法求素数
- 一步一步学设计模式——代理模式
- mysql 用case when 解决比较时间大小后在排序的问题
- 内容不符输入框变色
- Java 并发 --- CountDownLatch源码分析
- 带缓存的I/O操作代码示例(复习)
- Android换肤原理和Android-Skin-Loader框架解析
- 改善深层神经网络第一周-Initialization
- Leetcode 65:Valid Number
- 数组的基础和习题
- java程序员从笨鸟到菜鸟之(三十六)IO流之字符流
- Neutron中关于Devstack的源码分析
- GitLab的安装及使用教程