Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说Java并发工具类CountDownLatch原理剖析,希望能够帮助你!!!。
本篇文章的主要内容如下:
1:举例说明CountDownLatch和Thread的join()方法 2:CountDownLatch的原理剖析 3:CountDownLatch的源码解析
在实际的项目中,我们可能碰到如下的需求:
A线程需要用到B线程,C线程,D线程的结果
像上面的需求我们怎样利用程序去实现的。
实现一:熟悉Thread类的同学很快就能够想到join()方法,程序如下:
public class ThreadJoinTest { private static int totalCount; private static int count1; private static int count2; private static int count3; public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> count1 = 10); Thread t2 = new Thread(() -> count2 = 20); Thread t3 = new Thread(() -> count3 = 30); t1.start(); t2.start(); t3.start(); t1.join(); t2.join(); t3.join(); totalCount = count1 + count2 + count3; System.out.println("totalCount=" + totalCount); } }
运行结果如下:
上面的程序无论运行多少次都是同样的结果,我们看一下Thread的join()方法的原理:如果线程thread调用join()方法,那么它会阻塞当前正在运行的线程,记着是阻塞当前正在运行的线程,而不是阻塞thread线程,直到thread线程运行结束,才会继续往下执行。
例如上面的例子中主线程执行到t1.join()方法时,主线程会被阻塞,直到线程t1运行结束,才能继续往下执行t2.join(),当执行t2.join()方法时,主线程序又被阻塞直到线程t2运行结束,以此类推,我们想象以下如果不调用join()方法,那结果是无法预知的。
实现二:利用CountDownLatch实现 public class CountDownLatchTest { private static int totalCount; private static int count1; private static int count2; private static int count3; private static CountDownLatch cdl = new CountDownLatch(3); public static void main(String[] args) throws InterruptedException { new Thread(()->{ count1=10; cdl.countDown(); }).start(); new Thread(()->{ count2=20; cdl.countDown(); }).start(); new Thread(()->{ count3=30; cdl.countDown(); }).start(); cdl.await(); totalCount = count1+count2+count3; System.out.println("totalCount=" + totalCount); } }
运行结果如下:
从上面的运行结果可以看出,CountDownLatch也能实现join的效果,那它的实现原理是什么呢?和join()方法又有哪些区别呢?接下来从源码角度去揭开它的原理。
CountDownLatch是一个并发工具类,它允许一个线程或者多个线程等待,直到一系列操作在其他线程被完成,这句话蕴含了两层含义:
第一层含义:允许一个线程或者多个线程等待,是说明调用它的await()方法会使当前运行的线程阻塞。 第二层含义:直到一系列操作在其他线程被完成,这个完成点就是调用了countDown()方法
CountDownLatch构造函数接收一个计数器,调用一次countDown()方法,计数器就会减1,只有计数器变为0时,等待的线程才能从await()方法返回继续执行下面的代码,例如如果初始化的计数器是2,那么必须调用2次countDown()方法,被阻塞的线程才能从await()中返回,继续执行下面的代码。
在我们启动一个服务时,可能有比较耗时的工作需要其他线程协助同时完成,只有这些工作完成后,主服务线程才能执行下去,我们可以利用Thread的join()方法完成,也可以通过CountDownLatch完成,但是两者又有所区别。
1:对于join(),必须线程逻辑完全执行,等待线程才能从阻塞中返回。 2:对与CountDownLatch则非常的灵活了,它能保证调用countDown()方法前的逻辑能够在等待线程前执行,countDown()方法后的代码逻辑那就不一定了。
CountDownLatch的底层实现逻辑就是AQS共享资源模式的逻辑,其中初始化的计数器就是AQS共享资源state。
await()方法会阻塞当前线程,直到出现下面的情况才返回
1:计数器变成了0 2:被其他线程中断
countDown()方法的作用就是将计数器减一
对于这个并发工具类总结四个重要的知识点如下:
1:CountDownLatch底层是通过AQS框架的共享模式操作完成的。 2:调用构造函数传递计数器count,并且计数器不能小于0,计数器会赋值给底层AQS的state。 3:调用await()会阻塞当前线程,直到线程被中断或者count==0才返回 4:每调用一次countDown()方法会将计时器count减一
上面我们知道了它的原理,接下来我们从源码角度看一下它的实现。
在CountDownLatch源码中有这样一段代码:
private static final class Sync extends AbstractQueuedSynchronizer { //设置共享资源state 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; } } }
在CountDownLatch中创建了一个内部类Sync,它继承了AQS,这也是AQS的经典用法,并且实现了两个重要的方法,尝试获取共享资源tryAcquireShared()和释放共享资源tryReleaseShared().说明底层使用的是AQS框架的共享模式。对于AQS共享模式下资源的获取和释放不熟悉的请看这一篇文章。
public CountDownLatch(int count) { if (count < 0) throw new IllegalArgumentException("count < 0"); this.sync = new Sync(count); }
上面是它的构造函数,传递一个参数计时器count,并且count必须大于等于0,如果小于0会抛出异常,然后它直接调用了Sync的构造函数,我们从Sync的构造函数可知,其实传递的count就是赋值给了AQS的共享资源变量state。
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); }
上面的代码是CountDownLatch中的await()方法的源码,它底层直接调用了Sync的acquireSharedInterruptibly()方法,这个方法有两种情况返回:
第一种:被其他线程中断 第二种:tryAcquireShared()<0
那tryAcquireShard()什么时候会小于0呢?这在Sync中可以找到答案。
上面我们知道了要想从await()方法返回,要么线程被中断,要么tryAcquireShared()<0,而此方法只有count==0时才小于0,上面我们在创建CountDownLatch对象时传递的共享资源是count,所以我们做的就是将count变成0,那就是我们调用countDown()方法了。
public void countDown() { sync.releaseShared(1); }
上面的countDown()方法直接调用了Sync的releaseShared()方法
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。