Java并发学习(十七)-并发工具CountDownLatch
来源:互联网 发布:java贵美商城 编辑:程序博客网 时间:2024/06/02 02:20
另一个并发工具,CountDownLatch,和CyclicBarrier功能上有点类似,但是在实现上和是不同的。
What is CountDownLatch
简单的说,也是一个计数器,和CyclicBarrier相比,一定方面是更加灵活的,CountDownLatch可以在任意代码处通知自己已完成,等所有都做完时,主线程就可以接下来运行了。有需要可参看:Java并发学习(十六)-并发工具CyclicBarrier 。
结构
CountDownLatch里面维护这一个AbstractQueuedSynchronizer的子类,其实就是基于AQS的共享锁的应用。
看看Sync的代码:
private static final class Sync extends AbstractQueuedSynchronizer { //count去初始化state。 Sync(int count) { setState(count); } //获取state。 int getCount() { return getState(); } //重写父类方法,获取共享锁。 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; } /** * 释放锁。 * 自减state。 */ 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; } } }
上述代码只是简单的对AQS的state变量的CAS原子操作。
下面通过主要方法await
和countDown
来分析里面的实现逻辑。
await
一般我们用关于锁的方法时,都是lock和unlock,但是这里是await,你可以理解为,当所有线程执行完后,再需要执行的线程a,那么这个a就可以先await,等到其他的执行完,a就可以执行了。
首先执行await
:
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
再看父类的acquireSharedInterruptibly
方法:
public final void acquireSharedInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //<0,代表没有获取到锁,所以需要自旋方式再次尝试, //结果可能被park挂起,或者获取到锁 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
我们再看看子类重写的tryAcquireShared
方法:
//重写父类方法,获取共享锁。 protected int tryAcquireShared(int acquires) { return (getState() == 0) ? 1 : -1; }
当state为0时候,结果为1,放到acquireSharedInterruptibly
里面就是获取到锁;
而当state不为0,则结果为-1,也就是代表没有获取到锁。
而最开始CountDownLatch的构造方法:
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
所以count会传入大于0的,代表等待着count个线程。
现在就明了了,调用这个方法是用来阻塞的。
countDown
countDown什么时候用呢?
上面说过,其他的众多运行线程,countDown方法就是其他众多运行线程用,当你的工作线程运行完后,旧值性countDown方法。
public void countDown() { sync.releaseShared(1); }
再看父类的releaseShared方法:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
tryReleaseShared方法在上面Sync里面,主要就是对count值自减,而doReleaseShared则是unpark唤醒后继符合条件线程。
具体使用例子
从Java doc里面可以看到两个典型例子,这里以例说明。
任务分割情况:
class Driver2 { void main() throws InterruptedException { CountDownLatch doneSignal = new CountDownLatch(N); //线程池执行 Executor e = ... for (int i = 0; i < N; ++i) // create and start threads e.execute(new WorkerRunnable(doneSignal, i)); //等待,主线程main的await后面代码挂起等待 doneSignal.await(); //这后面方法,必须等所有其他的都countDown一次,才会被唤醒执行。 afterawait(); } } class WorkerRunnable implements Runnable { private final CountDownLatch doneSignal; private final int i; WorkerRunnable(CountDownLatch doneSignal, int i) { this.doneSignal = doneSignal; this.i = i; } public void run() { try { doWork(i); //我做完了,我利用countDown通知下,但是不阻塞当前线程 doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } //具体工作方法 void doWork() { ... } }
任务分割情况,就是把让其他众多线程a1,a2,a3…..全部运行完后,主线程b才能够执行。注意需要共用这一个CountDownLatch。
多控制计数器:
下面是一对类,其中一组工作线程使用两个倒计时锁存器:
- 首先是一个启动信号,防止任何工人进行,直到司机准备好进行;
- 第二个是完成信号,允许驾驶员等待所有工人完成。
class Driver { // ... void main() throws InterruptedException { //定义两个CountDownLatch CountDownLatch startSignal = new CountDownLatch(1); CountDownLatch doneSignal = new CountDownLatch(N); //运行一组线程 for (int i = 0; i < N; ++i) new Thread(new Worker(startSignal, doneSignal)).start(); //做些事 doSomethingElse(); //做完后,调用startSiganl的countDown startSignal.countDown(); //做另外一件事 doSomethingElse(); //等待doneSignal的信号 doneSignal.await(); } } class Worker implements Runnable { private final CountDownLatch startSignal; private final CountDownLatch doneSignal; Worker(CountDownLatch startSignal, CountDownLatch doneSignal) { this.startSignal = startSignal; this.doneSignal = doneSignal; } public void run() { try { //首先等待startSignal的信号 startSignal.await(); //等到信号了,做事 doWork(); //对doneSignal发出信号,说做好了 doneSignal.countDown(); } catch (InterruptedException ex) {} // return; } void doWork() { ... } }
与CyclicBarrier对比
从功能和实现上,CountDownLatch还是有点区别的:
- CyclicBarrier内部是使用ReentrantLock和Condition来实现等待和唤醒操作,而CountDownLatch则是直接利用AQS,采用共享锁来实现。
- CyclicBarrier可以重利用,而CountDownLatch则只能使用一次。
- CountDownLatch的作用是允许1或N个线程await等待其他线程完成(countDown)执行;而CyclicBarrier则是允许N个线程相互等待,即等到最后一个进入的去唤醒所有等待的。
与join对比
在Thread里面,还有个join方法,用于即阻塞当前线程直到执行join的线程运行完:
public class JoinCountDownLatchTest { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(new Runnable() { public void run() { System.out.println("parser1 finish"); } }); Thread thread2 = new Thread(new Runnable() { public void run() { System.out.println("parser2 finish"); } }); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("all parser finish"); }}
运行结果:
所以对与join也有等待线程的功能,但是来看join的代码实现:
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis(); long now = 0; //判断等待时间 if (millis < 0) { throw new IllegalArgumentException("timeout value is negative"); } //默认等待时间 if (millis == 0) { //如果当前线程仍然存活,那么就一直等待 while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
最后,直到join线程中止后,线程的this.notifyAll会被调用。所以main线程最后会被唤醒,当然,join方法里面使用了传统的synchronized关键字,以及wait,notify,notifyAll等方法,JDK不推荐在线程实例上使用wait,notify和notifyAll方法。
参考资料:
1. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CountDownLatch.html
2. http://ifeve.com/talk-concurrency-countdownlatch/
- Java并发学习(十七)-并发工具CountDownLatch
- Java并发学习之十七——线程同步工具之CountDownLatch
- Java并发学习之十七——线程同步工具之CountDownLatch
- java并发学习----CountDownLatch
- Java并发工具类CountDownLatch
- java 并发工具类-CountDownLatch
- Java并发工具类CountDownLatch
- 线程并发工具--CountDownLatch
- Java并发工具类CountDownLatch和CyclicBarrier
- Java并发工具类之CountDownLatch
- Java并发工具类CountDownLatch和CyclicBarrier
- Java并发工具类之CountDownLatch
- Java并发学习笔记(三)-闭锁CountDownLatch
- 深入学习java并发编程:CountDownLatch、CyclicBarrier
- Java并发同步器--CountDownLatch
- Java并发之CountDownLatch
- Java并发之CountDownLatch
- Java并发编程:CountDownLatch
- Spark:Java实现Top N
- RxJava源码分析(一)
- python中with as 用法
- 带你逐步深入了解SSH框架——struts2拦截器
- java 构造方法,static关键字,main方法修饰解释
- Java并发学习(十七)-并发工具CountDownLatch
- java构造方法练习
- bluemail-iOS使用SMTP发送邮件 send mail via SMTP at iOS
- typedef的用法
- Spark:Scala实现Top N
- 互联网大会上的网约车的未来
- html_entity_decode
- python里使用enum库枚举类型时枚举值相同
- 【python运维】系统进程管理方法