最近遇到一个问题需要主线程等待所有的子线程结束,才能开始执行,统计所有的子线程执行结果,返回,网上翻阅各种资料,最后记录一下,找到七种方案

第一种:while循环

对于“等待所有的子线程结束”的问题,最开始想到的是使用while循环进行轮询:

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);});t.start();String end = "";//t.getState() != State.TERMINATED这两种判断方式都可以while(t.isAlive() == true){end = getTheTimeInMilliseconds();}System.out.println("end = " + end);

但是这样太消耗CPU,所以我在while循环里加入了暂停,让其歇会:

        while(t.isAlive() == true){end = System.currentTimeMillis();try {Thread.sleep(10);}catch (InterruptedException e){e.printStackTrace();}}

这样做的结果虽然cpu消耗减少,但是数据不准确了

第二种:Thread的join()方法

将 方法1 中的while循环代码更改如下

        try {t.join();//注意这里} catch (InterruptedException e) {e.printStackTrace();}

使用join()方法,join()方法的作用,是等待这个线程结束;(t.join()方法阻塞调用此方法的线程,直到线程t完成,此线程再继续)

第三种:synchronized 的等待唤醒机制

第二种方法的确实现了,接着我又想到了多线程的等待唤醒机制,思路是:子线程启动后主线程等待,子线程结束后唤醒主线程。于是有了下面的代码:

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);Object lock = new Object();Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);lock.notify();});t.start();try {lock.wait();//主线程等待} catch (InterruptedException e) {e.printStackTrace();}String end = getTheTimeInMilliseconds();System.out.println("end = " + end);

但是这样运行结果会抛出两个异常:

由于对wait()和notify()的理解并不是很深刻,所以我最开始并不清楚为什么会出现这样的结果,因为从报错顺序来看子线程并没有提前唤醒,于是我到处翻阅资料,最后得出的结论是调用wait()方法时需要获取该对象的锁,Object文档里是这么说的:

当前线程必须拥有该对象的监视器。如果当前线程不是对象监视器的所有者,抛异常IllegalMonitorStateException。

所以上面的代码需要改成这样:

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);Object lock = new Object();Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);synchronized (lock) {//获取对象锁lock.notify();//子线程唤醒}});t.start();try {synchronized (lock) {//这里也是一样lock.wait();//主线程等待}} catch (InterruptedException e) {e.printStackTrace();}String end = getTheTimeInMilliseconds();System.out.println("end = " + end);

这样的确得出了结果,但是主线程有可能先wait子线程,在notify,也就是说,如果子线程在主线程wait前,调用了notify,会导致主线程无限等待,所以这个思路还是有一定漏洞的

第四种:CountDownLatch

第四种方式可以等待多个线程结束,就是使用java.util.concurrent包下的CountDownLatch类

简单来说,CountDownLatch类是一个计数器,可以设置初始线程数(设置后不能改变),在子线程结束时调用countDown()方法可以使线程数减一,最终为0的时候,调用CountDownLatch的成员方法wait()的线程就会取消BLOKED阻塞状态,进入RUNNABLE从而继续执行。下面上代码:

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);int threadNumber = 1;//参数为线程个数final CountDownLatch cdl = new CountDownLatch(threadNumber);Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);//此方法是CountDownLatch的线程数-1cdl.countDown();});t.start();try {//需要捕获异常,当其中线程数为0时这里才会继续运行cdl.await();} catch (InterruptedException e) {e.printStackTrace();}String end = getTheTimeInMilliseconds();System.out.println("end = " + end);

第五种:Future

又想到线程池,线程池的submit()的返回对象Future接口有一个get()方法也可以阻塞当前线程(其实该方法主要用途是获取子线程的返回值),所以第五种方法也出来了:

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);ExecutorService executorService = Executors.newFixedThreadPool(1);Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);});//子线程启动Future future = executorService.submit(t);try {future.get();//需要捕获两种异常}catch (InterruptedException e){e.printStackTrace();}catch (ExecutionException e){e.printStackTrace();}String end = getTheTimeInMilliseconds();System.out.println("end = " + end);executorService.shutdown();

这里, ThreadPoolExecutor 是实现了 ExecutorService的方法, sumbit的过程就是把一个Runnable接口对象包装成一个 Callable接口对象, 然后放到 workQueue里等待调度执行. 当然, 执行的启动也是调用了thread的start来做到的, 只不过这里被包装掉了. 另外, 这里的thread是会被重复利用的, 所以这里要退出主线程, 需要执行以下shutdown方法以示退出使用线程池. 扯远了.

这种方法是得益于Callable接口和Future模式, 调用future接口的get方法, 会同步等待该future执行结束, 然后获取到结果. Callbale接口的接口方法是 V call(); 是可以有返回结果的, 而Runnable的 void run(), 是没有返回结果的. 所以, 这里即使被包装成Callbale接口, future.get返回的结果也是null的.如果需要得到返回结果, 建议使用Callable接口.

第六种:BlockingQueue

同时,在concurrent包中,还提供了BlockingQueue(队列)来操作线程,BlockingQueue的主要的用法是在线程间安全有效的传递数据,因此,第六种方法也出来了:

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);//数组型队列,长度为1BlockingQueue queue = new ArrayBlockingQueue(1);Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);try {//在队列中加入数据queue.put("OK");} catch (InterruptedException e) {e.printStackTrace();}});t.start();try {queue.take();//主线程在队列中获取数据,take()方法会阻塞队列,ps还有不会阻塞的方法} catch (InterruptedException e) {e.printStackTrace();}String end = getTheTimeInMilliseconds();System.out.println("end = " + end);

第七种:CyclicBarrier

第七种方式,还是concurrent包,只不过这次试用CyclicBarrier类:

CyclicBarrier字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。

        //开始计时String start = getTheTimeInMilliseconds();System.out.println("start = " + start);//参数为线程数CyclicBarrier barrier = new CyclicBarrier(2);Thread t = new Thread(() -> {//子线程进行字符串连接操作int num = 1000;String s = "";for (int i = 0; i < num; i++) {s += "Java" + i;}System.out.println("t Over s =" + s);try {//阻塞barrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}});t.start();try {barrier.await();//也阻塞,并且当阻塞数量达到指定数目时同时释放} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}String end = getTheTimeInMilliseconds();System.out.println("end = " + end);

实际是上面这种方法是不太严谨的,因为在子线程阻塞之后如果还有代码是会继续执行的,当然本例中后面是没有代码可执行了,可以近似理解为是子线程的运行时间。

问题

扒拉出这么方法都可以实现主线程等待子线程的方法,上个问题:

几万,几十万的数据情况下,只能一条信息一条信息发送,需要优化消费时间

保证每条数据都被消费掉,并且统计所有失败的记录,失败的原因

小弟综合实际写的代码,上demo,求各路大佬指教代码中的缺陷,因为这块不熟,面向百度编程使用的事淋漓尽致,所以老感觉有坑,但是又不知道在哪,贼尴尬

    /*** 业务代码精简版*/private static AtomicInteger atomicInteger = new AtomicInteger(0);private static final CountDownLatch latch = new CountDownLatch(100);private static ExecutorService pool = new ThreadPoolExecutor(2, 4,1000, TimeUnit.SECONDS,new LinkedBlockingDeque<Runnable>(1000),new ThreadPoolExecutor.DiscardOldestPolicy());public static void main(String[] args) {System.out.println("主线程开始执行…… ……");// 需要统计每个数据的消费结果List<Map<String, Integer>> resultMap = new ArrayList<>();for (int i = 0; i < 100; i++) {pool.execute(() -> {try {synchronized (TestThread.class){Map<String, Integer> map = new Hashtable<>();// 假装获取了每个数据消费结果map.put("success", 0);resultMap.add(map);atomicInteger.getAndIncrement();}} catch (Exception e) {e.printStackTrace();} finally {latch.countDown();}});}try {latch.await();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("成功数量" + atomicInteger.get());System.out.println(resultMap);}

【多线程】学习记录七种主线程等待子线程结束之后在执行的方法相关推荐

  1. Java多线程之----主线程会等待子线程结束再结束么,怎么让主线程等待子线程结束呐?

    首先给出结论: 主线程和子线程之间没有谁先谁后结束这种关联,它们只是各自负责自己的线程任务,如果该线程的任务结束了,该线程自然会结束运行. talk is cheap,show me the code ...

  2. c++主线程等待子线程结束_简单明了的 Python 多线程来了 | 原力计划

    作者 | 万里羊责编 | 王晓曼出品 | CSDN博客线程和进程计算机的核心是CPU,它承担了所有的计算任务,就像是一座工厂在时刻运行.如果工厂的资源有限,一次只能供一个车间来使用,也就是说当一个车间 ...

  3. VC++ 中主线程等待子线程结束的方法

    void WaitForThreadExit(void) {DWORD dwRet; //返回值MSG msg; int wait_count=4; //线程句柄有4个int nExitThreadC ...

  4. Java多线程协作CountDownLatch,主线程等待子线程结束

    CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 主要方法 public CountDownLatch(int count);构造方 ...

  5. java 主线程等待_Java实现主线程等待子线程

    本文介绍两种主线程等待子线程的实现方式,以5个子线程来说明: 1.使用Thread的join()方法,join()方法会阻塞主线程继续向下执行. 2.使用Java.util.concurrent中的C ...

  6. 如何实现java主线程等待子线程执行完毕之后再执行?

    本文转自:问题:如何实现java主线程等待子线程执行完毕之后再执行? - jseven - 博客园 点击关注强哥,查看更多精彩文章呀 工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完 ...

  7. Java并发编程原理与实战六:主线程等待子线程解决方案

    Java并发编程原理与实战六:主线程等待子线程解决方案 参考文章: (1)Java并发编程原理与实战六:主线程等待子线程解决方案 (2)https://www.cnblogs.com/pony1223 ...

  8. java等待5秒_Java并发编程-主线程等待子线程解决方案

    主线程等待所有子线程执行完成之后,再继续往下执行的解决方案 public class TestThread extends Thread { public void run() { System.ou ...

  9. Java主线程等待子线程、线程池

    public class TestThread extends Thread { public void run() { System.out.println(this.getName() + &qu ...

最新文章

  1. php使用NuSoap产生webservice结合WSDL让asp.net调用
  2. xadmin后台页面定制和添加服务器监控组件
  3. Xamarin.Android编译提示找不到mscorlib.dll.so文件
  4. 汇编中的通用寄存器、标志寄存器、段寄存器
  5. 个人工作日报模板_2020最新销售店长个人年度工作计划模板精选3篇
  6. Codeforces Round #654 (Div. 2)
  7. 【JavaScript】编写一个炫彩的网页时钟
  8. jmeter tps指标在哪里看_性能之路——性能测试连载 (3)-性能指标
  9. PairSCL:句子对级别的有监督对比学习方法
  10. linux apache访问日志,linux分析apache日志获取最多访问的前10个IP
  11. 自己攒的正则表达式---判断汉字、字符但不要数字
  12. 如何在Axure使用iconfont图标库里的图标
  13. 学生管理-axios优化
  14. 计算机关机慢怎么解决方法,电脑关机很慢,详细教您win7电脑关机很慢的解决方法...
  15. 产品思维训练 | 你的项目总是不能按期上线,你会如何解决?
  16. 视频编码中CBR和VBR的区别
  17. jQuery根据ID删除元素
  18. React-Redux使用方法
  19. 韩国媒体:中国手机的崛起,都是依靠“性价比”?
  20. 顶级智囊支招 丰泽智慧城市建设

热门文章

  1. Helios与Katana的区别
  2. etc/ld.so.conf的使用说明
  3. 配置Xmanager连接linux
  4. NKOJ 1791 Party at Hali-Bula(树状DP)
  5. golang中的redigo
  6. golang中的执行规则
  7. golang中的方法
  8. UART0串口编程(一):通信协议设计;RS232标准
  9. springmvc五:使用pojo作为参数
  10. objective-c 方法加号(+) 减号(-)