你有一个思想,我有一个思想,我们交换后,一个人就有两个思想
If you can NOT explain it simply, you do NOT understand it well enough
现陆续将Demo代码和技术文章整理在一起 Github实践精选 ,方便大家阅读查看,本文同样收录在此,觉得不错,还请Star
前言并发编程的三大核心是分工,同步和互斥。在日常开发中,经常会碰到需要在主线程中开启多个子线程去并行的执行任务,并且主线程需要等待所有子线程执行完毕再进行汇总的场景,这就涉及到分工与同步的内容了
在讲 时,提到过 join() 规则,使用 join() 就可以简单的实现上述场景:
@Slf4j public class JoinExample { public static void main(String[] args) throws InterruptedException { Thread thread1 = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("Thread-1 执行完毕"); } }, "Thread-1"); Thread thread2 = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("Thread-2 执行完毕"); } }, "Thread-2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); log.info("主线程执行完毕"); } }运行结果:
整个过程可以这么理解
我们来查看 join() 的实现源码:
其实现原理是不停的检查 join 线程是否存活,如果 join 线程存活,则 wait(0) 永远的等下去,直至 join 线程终止后,线程的 this.notifyAll() 方法会被调用(该方法是在 JVM 中实现的,JDK 中并不会看到源码),退出循环恢复主线程执行。很显然这种循环检查的方式比较低效
除此之外,使用 join() 缺少很多灵活性,比如实际项目中很少让自己单独创建线程(原因在 我会手动创建线程,为什么要使用线程池? 中说过)而是使用 Executor, 这进一步减少了 join() 的使用场景,所以 join() 的使用在多数是停留在 demo 演示上
那如何实现文中开头提到的场景呢?
CountDownLatchCountDownLatch, 直译过来【数量向下门闩】,那肯定里面有计数器的存在了。我们将上述程序用 CountDownLatch 实现一下,先让大家有个直观印象
@Slf4j public class CountDownLatchExample { private static CountDownLatch countDownLatch = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { // 这里不推荐这样创建线程池,最好通过 ThreadPoolExecutor 手动创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(2); executorService.submit(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("Thread-1 执行完毕"); //计数器减1 countDownLatch.countDown(); } }); executorService.submit(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { log.info("Thread-2 执行完毕"); //计数器减1 countDownLatch.countDown(); } }); log.info("主线程等待子线程执行完毕"); log.info("计数器值为:" + countDownLatch.getCount()); countDownLatch.await(); log.info("计数器值为:" + countDownLatch.getCount()); log.info("主线程执行完毕"); executorService.shutdown(); } }运行结果如下:
结合上述示例的运行结果,相信你也能猜出 CountDownLatch 的实现原理了:
初始化计数器数值,比如为2
子线程执行完则调用 countDownLatch.countDown() 方法将计数器数值减1
主线程调用 await() 方法阻塞自己,直至计数器数值为0(即子线程全部执行结束)
不知道你是否注意,countDownLatch.countDown(); 这行代码可以写在子线程执行的任意位置,不像 join() 要完全等待子线程执行完,这也是 CountDownLatch 灵活性的一种体现