知识没有狗粮来的直接,所以先上狗粮!
线程同步器
-
CountDownlatch原理刨析
-
场景:
-
在日常开发中会遇到需要在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有的子线程执行完毕后再进行汇总的场景,对于这种情况在CountDownLatch之前一般使用线程的join()方法来实现此需求,但是join方法不够灵活,不能满足场景需要,因此有了CountDownLatch。
-
实现方式如下:
public class CountDownLatch2 { private static volatile CountDownLatch countDownLatch = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { countDownLatch.countDown(); } System.out.println("child thread over"); } }); executorService.submit(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { countDownLatch.countDown(); } System.out.println("child thread over"); } }); countDownLatch.await(); System.out.println("all child over"); executorService.shutdown(); } }
以上代码中直接通过线程池创建了两个线程,分别沉睡一秒,并且在执行结束会调用countDown方法,在最后调用await方法等待所有的子线程执行完毕才算结束。
CountDownLatch与join的区别是调用子线程的join方法,该线程会一直被阻塞直到子线程执行完毕,而CountDownLatch则使用计数器来允许子线程运行完毕然后计数器减1,因此CountDownLatch可以在子线程运行时让await方法返回而不需要等到线程结束。
-
-
原理: CountDownLatch内部有一个计数器,并且这个计数器是递减的。
从类图中可以看到CountDownLatch是使用AQS来实现的,CountDownLatch内部的计数器的值是相当于AQS的state,也就是通过AQS的state值来表示计数器。
-
void await()
-
当线程调用await方法后,当前线程会被阻塞,接触阻塞的方式有两种:
- 只有当所有的线程都调用了CountDownLatch的countDown方法后(也就是计数器的值变为0时)
- 其它线程调用interrupt()方法中断了当前线程后才会释放
public void await() throws InterruptedException { sync.acquireSharedInterruptibly(1); } //AQS中的方法 public final void acquireSharedInterruptibly(int arg) throws InterruptedException { //如果线程被中断则抛出异常 if (Thread.interrupted()) throw new InterruptedException(); //查看当前计数器的值是否为0,为0则直接返回,否则进入AQS的队列等待 if (tryAcquireShared(arg) < 0) doAcquireSharedInterruptibly(arg); }
该方法中在线程获取资源时是可以被中断的,并且获取的资源是共享资源,acquireSharedInterruptibly方法中首先判断线程是否被中断,如果被中断则抛出异常否则调用tryAcquireShared方法查看当前状态值是否为0,为0则直接返回,否则让当前线程阻塞放入AQS队列中。
-
-
boolean await(long timeout, TimeUnit unit )
该方法中跟上一个方法效果一样,但是这个方法有时间参数,也就是如果该线程超过了设定的时间,则会超时然后抛出false。
-
void countDown()
public void countDown() { //调用sync的releaseShared方法 sync.releaseShared(1); } public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { //AQS释放资源的方法 doReleaseShared(); return true; } return false; } protected boolean tryReleaseShared(int releases) { // Decrement count; signal when transition to zero //循环进行CAS,直到当前线程成功完成CAS使计数器减1并更新到state for (;;) { int c = getState(); //如果当前状态值为0则直接返回 if (c == 0) return false; //使用CAS让计数器减1 int nextc = c-1; if (compareAndSetState(c, nextc)) return nextc == 0; } }
在如上代码中首先获取到状态值,然后判断状态值是否为0,如果为0则直接返回,否则通过CAS将计数器递减1,如果CAS失败则循环重试。
-
-
总结:
CountDownLatch相比于join方法来实现线程同步来说更具有灵活性。另外CountDownLatch的实现也是基于AQS,CountDownLatch中的计数器的值则是通过AQS中的state值来维护的,当计数器变为0后,当前线程还需要调用AQS中的doReleaseShared方法来激活由于调用await方法而被阻塞的线程。
-
虽然每天要陪女票。但是每天的学习不能停止,依然是个会敲代码的汤姆猫!