文档章节

Java如何等待子线程执行结束

l
 lintalkliu
发布于 2016/05/12 22:21
字数 2253
阅读 4
收藏 0
点赞 2
评论 0

Java如何等待子线程执行结束

今天讨论一个入门级的话题, 不然没东西更新对不起空间和域名~~

工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的产景, 这时候就需要使用线程了.

一个线程启动之后, 是异步的去执行需要执行的内容的, 不会影响主线程的流程,  往往需要让主线程指定后, 等待子线程的完成. 这里有几种方式.

站在 主线程的角度, 我们可以分为主动式和被动式.

主动式指主线主动去检测某个标志位, 判断子线程是否已经完成. 被动式指主线程被动的等待子线程的结束, 很明显, 比较符合人们的胃口. 就是你事情做完了, 你告诉我, 我汇总一下, 哈哈.

那么主线程如何等待子线程工作完成呢. 很简单, Thread 类给我们提供了join 系列的方法, 这些方法的目的就是等待当前线程的die. 举个例子.

 

public  class  Threads {

 

      public  static  void  main (String[]  args ) {

           SubThread thread  =  new  SubThread () ;

          thread . start () ;

          //主线程处理其他工作,让子线程异步去执行.

           mainThreadOtherWork () ;

           System . out . println ( “now waiting sub thread done.” ) ;

          //主线程其他工作完毕,等待子线程的结束, 调用join系列的方法即可(可以设置超时时间)

           try  {

              thread . join () ;

          }  catch  ( InterruptedException e) {

              e . printStackTrace () ;

          }

          System . out . println ( “now all done.” ) ;

     }

 

      private  static  void  mainThreadOtherWork () {

          System . out . println ( “main thread work start” ) ;

           try  {

               Thread . sleep ( 3000L ) ;

          }  catch  ( InterruptedException e) {

              e . printStackTrace () ;

          }

           System . out . println ( “main thread work done.” ) ;

     }

 

      public  static  class  SubThread  extends  Thread{

           @Override

           public  void  run () {

               working () ;

          }

 

           private  void  working () {

               System . out . println ( “sub thread start working.” ) ;

               busy () ;

               System . out . println ( “sub thread stop working.” ) ;

          }

 

           private  void  busy () {

               try  {

                    sleep ( 5000L ) ;

              }  catch  ( InterruptedException e) {

                   e . printStackTrace () ;

              }

          }

          

     }

}

 

本程序的数据有可能是如下:

  1. main thread work start
  2. sub thread start working.
  3. main thread work done.
  4. now waiting sub thread done.
  5. sub thread stop working.
  6. now all done.

忽略标号, 当然输出也有可能是1和2调换位置了. 这个我们是无法控制的. 我们看下线程的join操作, 究竟干了什么.

 

     public  final  void  join()  throws  InterruptedException {

     join(0) ;

    }

这里是调用了

     public  final  synchronized  void  join( long  millis) 

方法, 参数为0, 表示没有超时时间, 等到线程结束为止. join(millis)方法里面有这么一段代码:

          while  (isAlive()) {

          wait(0) ;

         }

说明, 当线程处于活跃状态的时候, 会一直等待, 直到这里的isAlive方法返回false, 才会结束.isAlive方法是一个本地方法, 他的作用是判断线程是否已经执行结束. 注释是这么写的: 

Tests if this thread is alive. A thread is alive if it has been started and has not yet died.

 

可见, join系列方法可以帮助我们等待一个子线程的结束.

 

那么要问, 有没有另外一种方法可以等待子线程结束? 当然有的, 我们可以使用并发包下面的Future模式.

Future是一个任务执行的结果, 他是一个将来时, 即一个任务执行, 立即异步返回一个Future对象, 等到任务结束的时候, 会把值返回给这个future对象里面. 我们可以使用ExecutorService接口来提交一个线程.

 

public  class  Threads {

 

      static  ExecutorService  executorService  =  Executors . newFixedThreadPool ( 1 ) ;

     

      @SuppressWarnings ( “rawtypes” )

      public  static  void  main (String[]  args )  throws  InterruptedException ,  ExecutionException {

          SubThread thread  =  new  SubThread () ;

//        thread.start();

           Future  future  =  executorService . submit (thread) ;

           mainThreadOtherWork () ;

          System . out . println ( “now waiting sub thread done.” ) ;

          future . get () ;

//        try {

//            thread.join();

//        } catch (InterruptedException e) {

//            e.printStackTrace();

//        }

          System . out . println ( “now all done.” ) ;

           executorService . shutdown () ;

     }

 

      private  static  void  mainThreadOtherWork () {

          System . out . println ( “main thread work start” ) ;

           try  {

              Thread . sleep ( 3000L ) ;

          }  catch  (InterruptedException e) {

              e . printStackTrace () ;

          }

          System . out . println ( “main thread work done.” ) ;

     }

 

      public  static  class  SubThread  extends  Thread{

           @Override

           public  void  run () {

               working () ;

          }

 

           private  void  working () {

              System . out . println ( “sub thread start working.” ) ;

               busy () ;

              System . out . println ( “sub thread stop working.” ) ;

          }

 

           private  void  busy () {

               try  {

                    sleep ( 5000L ) ;

              }  catch  (InterruptedException e) {

                   e . printStackTrace () ;

              }

          }

          

     }

     

}

 

这 里, 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接口.

 

通过队列来控制线程的进度, 是很好的一个理念. 我们完全可以自己搞个队列, 自己控制. 这样也可以实现. 不信看代码:

 

public  class  Threads {

 

//   static ExecutorService executorService = Executors.newFixedThreadPool(1);

      static  final  BlockingQueue < Integer >  queue  =  new  ArrayBlockingQueue < Integer > ( 1 ) ;

      public  static  void  main (String[]  args )  throws  InterruptedException ,  ExecutionException {

          SubThread thread  =  new  SubThread ( queue ) ;

          thread . start () ;

//        Future future = executorService.submit(thread);

           mainThreadOtherWork () ;

          System . out . println ( “now waiting sub thread done.” ) ;

//        future.get();

           queue . take () ;

//        try {

//            thread.join();

//        } catch (InterruptedException e) {

//            e.printStackTrace();

//        }

          System . out . println ( “now all done.” ) ;

//        executorService.shutdown();

     }

 

      private  static  void  mainThreadOtherWork () {

          System . out . println ( “main thread work start” ) ;

           try  {

              Thread . sleep ( 3000L ) ;

          }  catch  (InterruptedException e) {

              e . printStackTrace () ;

          }

          System . out . println ( “main thread work done.” ) ;

     }

 

      public  static  class  SubThread  extends  Thread{

          

           private  BlockingQueue < Integer >  queue ;

          

           /**

           *  @param   queue

           */

           public  SubThread ( BlockingQueue < Integer >  queue ) {

               this . queue  =  queue ;

          }

 

           @Override

           public  void  run () {

               try {

               working () ;

              } finally {

                    try  {

                         queue . put ( 1 ) ;

                   }  catch  (InterruptedException e) {

                        e . printStackTrace () ;

                   }

              }

              

          }

 

           private  void  working () {

              System . out . println ( “sub thread start working.” ) ;

               busy () ;

              System . out . println ( “sub thread stop working.” ) ;

          }

 

           private  void  busy () {

               try  {

                    sleep ( 5000L ) ;

              }  catch  (InterruptedException e) {

                   e . printStackTrace () ;

              }

          }

          

     }

     

}

 

这 里是得益于我们用了一个阻塞队列, 他的put操作和take操作都会阻塞(同步), 在满足条件的情况下.当我们调用take()方法是, 由于子线程还没结束, 队列是空的, 所以这里的take操作会阻塞, 直到子线程结束的时候, 往队列里面put了个元素, 表明自己结束了. 这时候主线程的take()就会返回他拿到的数据. 当然, 他拿到什么我们是不必去关心的.

以上几种情况都是针对子线程只有1个的时候. 当子线程有多个的时候, 情况就不妙了.

第一种方法, 你要调用很多个线程的join, 特别是当你的线程不是for循环创建的, 而是一个一个创建的时候.

第二种方法, 要调用很多的future的get方法, 同第一种方法.

第三种方法, 比较方便一些, 只需要每个线程都在queue里面 put一个元素就好了.但是, 第三种方法, 这个队列里的对象, 对我们是毫无用处, 我们为了使用队列, 而要不明不白浪费一些内存, 那有没有更好的办法呢?

有的, concurrency包里面提供了好多有用的东东, 其中, CountDownLanch就是我们要用的.

CountDownLanch 是一个倒数计数器, 给一个初始值(>=0), 然后没countDown一次就会减1, 这很符合等待多个子线程结束的产景: 一个线程结束的时候, countDown一次, 直到所有都countDown了 , 那么所有子线程就都结束了.

先看看CountDownLanch有哪些方法:

await: 会阻塞等待计数器减少到0位置. 带参数的await是多了等待时间.

countDown: 将当前的技术减1

getCount(): 返回当前的计数

显而易见, 我们只需要在子线程执行之前, 赋予初始化countDownLanch, 并赋予线程数量为初始值.

每个线程执行完毕的时候, 就countDown一下.主线程只需要调用await方法, 可以等待所有子线程执行结束, 看代码:

 

public  class  Threads {

 

//   static ExecutorService executorService = Executors.newFixedThreadPool(1);

      static  final  BlockingQueue < Integer >  queue  =  new  ArrayBlockingQueue < Integer > ( 1 ) ;

      public  static  void  main (String[]  args )  throws  InterruptedException ,  ExecutionException {

           int  threads  =  5 ;

          CountDownLatch countDownLatch  =  new  CountDownLatch (threads) ;

           for ( int  i = 0 ; i < threads ; i ++ ){

              SubThread thread  =  new  SubThread ( 2000 * (i + 1 ) ,  countDownLatch) ;

              thread . start () ;

          }

//        Future future = executorService.submit(thread);

           mainThreadOtherWork () ;

          System . out . println ( “now waiting sub thread done.” ) ;

//        future.get();

//        queue.take();

          countDownLatch . await () ;

//        try {

//            thread.join();

//        } catch (InterruptedException e) {

//            e.printStackTrace();

//        }

          System . out . println ( “now all done.” ) ;

//        executorService.shutdown();

     }

 

      private  static  void  mainThreadOtherWork () {

          System . out . println ( “main thread work start” ) ;

           try  {

              Thread . sleep ( 3000L ) ;

          }  catch  (InterruptedException e) {

              e . printStackTrace () ;

          }

          System . out . println ( “main thread work done.” ) ;

     }

 

      public  static  class  SubThread  extends  Thread{

          

//        private BlockingQueue<Integer> queue;

           private  CountDownLatch  countDownLatch ;

           private  long  work ;

          

           /**

           *  @param  queue

           */

//        public SubThread(BlockingQueue<Integer> queue) {

//            this.queue = queue;

//            this.work = 5000L;

//        }

          

           public  SubThread ( long  work ,  CountDownLatch  countDownLatch ) {

//            this.queue = queue;

               this . countDownLatch  =  countDownLatch ;

               this . work  =  work ;

          }

 

           @Override

           public  void  run () {

               try {

               working () ;

              } finally {

//                 try {

//                      queue.put(1);

//                 } catch (InterruptedException e) {

//                      e.printStackTrace();

//                 }

                    countDownLatch . countDown () ;

              }

              

          }

 

           private  void  working () {

              System . out . println ( getName () + ” sub thread start working.” ) ;

               busy () ;

              System . out . println ( getName () + ” sub thread stop working.” ) ;

          }

 

           private  void  busy () {

               try  {

                    sleep ( work ) ;

              }  catch  (InterruptedException e) {

                   e . printStackTrace () ;

              }

          }

          

     }

}

此种方法也适用于使用 ExecutorService summit 的任务的执行.

另外还有一个并发包的类CyclicBarrier, 这个是(子)线程之间的互相等待的利器. 栅栏, 就是把大家都在一个地方堵住, 就像水闸, 等大家都完成了之前的操作, 在一起继续下面的操作. 不过就不再本篇的讨论访问内了.

本文转载自:原文:http://www.jiacheo.org/blog/262

共有 人打赏支持
l
粉丝 0
博文 6
码字总数 729
作品 0
南京
Java并发编程笔记之基础总结(二)

一.线程中断 Java 中线程中断是一种线程间协作模式,通过设置线程的中断标志并不能直接终止该线程的执行,而是需要被中断的线程根据中断状态自行处理。   1.void interrupt() 方法:中断线...

狂小白
07/17
0
0
Java多线程学习(五)线程间通信知识点补充

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀
04/16
0
0
Java 并发工具包 java.util.concurrent 用户指南

译序 本指南根据 Jakob Jenkov 最新博客翻译,请随时关注博客更新:http://tutorials.jenkov.com/java-util-concurrent/index.html。 本指南已做成中英文对照阅读版的 pdf 文档,有兴趣的朋友...

pior
2015/10/26
0
0
JAVA多线程和并发基础面试问答

Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一...

清风傲剑
2014/12/06
0
0
JAVA多线程和并发基础面试问答

原文链接 译文连接 作者:Pankaj 译者:郑旭东 校对:方腾飞 多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢...

雷神雨石
2014/07/19
0
2
JAVA多线程和并发基础面试问答

多线程和并发问题是Java技术面试中面试官比较喜欢问的问题之一。在这里,从面试的角度列出了大部分重要的问题,但是你仍然应该牢固的掌握Java多线程基础知识来对应日后碰到的问题。(校对注:...

LCZ777
2014/05/26
0
0
JAVA多线程和并发基础面试问答

Java多线程面试问题 1. 进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用。而线程是在进程中执行的一个任务。Java运行环境是一...

hanzhankang
2014/01/20
0
0
Java编程的逻辑 -- 并发章 -- 线程的基本协作机制

线程的基本协作 线程的基本协作示例 总结 线程的基本协作 多线程间除了竞争访问同一资源外,也经常需要相互协作的去执行一些任务。而对于协作的基本机制用的最多的无疑是wait/notify。 协作的...

HikariCP
06/22
0
0
多线程编程学习三(线程间通信).

一、概要 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一。可以说,使线程进行通信后,系统之间的交互性会更强大...

jmcui
2017/09/12
0
0
如何实现主线程需要等待子线程的结果,然后才能往下执行。

如何实现主线程需要等待子线程的结果,然后才能往下执行。 1. 使用FLAG标志位 [java] view plaincopy public class TestSync { private static int FLAG = 0; public static void main(Strin...

KeepMoving
2015/08/25
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

Win10专业版安装GIT后使用Git Bash闪退解决办法

百度后把过程和最终解决办法记录下来: 百度首先出来的解决办法如下: 来自:https://segmentfault.com/q/1010000012722511?sort=created 重启电脑 重新安装 安装到C盘 尝试网上的教程 \Git...

特拉仔
13分钟前
0
0
设计模式

1.装饰器模式 概念 允许向一个现有的对象添加新的功能,同时又不改变其结构。装饰者可以在所委托被装饰者的行为之前或之后加上自己的行为,以达到特定的目的。 实现 增加一个修饰类包裹原来的...

EasyProgramming
27分钟前
1
0
用python2和opencv进行人脸识别

一、安装cv2 sudo apt-get install python-opencv opencv-data 二、 Haar特征分类器 Haar特征分类器就是一个XML文件,该文件中会描述人体各个部位的Haar特征值。包括人脸、眼睛、嘴唇等等。 ...

wangxuwei
27分钟前
0
0
python模板中循环字典

{% for k,v in user.items %} {{ k}} {{ v}} {% endfor %}

南桥北木
56分钟前
0
0
Java8系列之重新认识HashMap

简介 Java为数据结构中的映射定义了一个接口java.util.Map,此接口主要有四个常用的实现类,分别是HashMap、Hashtable、LinkedHashMap和TreeMap,类继承关系如下图所示: 下面针对各个实现类...

HOT_POT
59分钟前
0
0
获取调用方的className

/** * 获取调用方的class * @return */private static String getInvoke() { StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); S......

iborder
今天
0
0
深入了解一下Redis的内存模型!

一前言 Redis是目前最火爆的内存数据库之一,通过在内存中读写数据,大大提高了读写速度,可以说Redis是实现网站高并发不可或缺的一部分。 我们使用Redis时,会接触Redis的5种对象类型(字符...

Java填坑之路
今天
1
0
从实践出发:微服务布道师告诉你Spring Cloud与Spring Boot他如何选择

背景 随着公司业务量的飞速发展,平台面临的挑战已经远远大于业务,需求量不断增加,技术人员数量增加,面临的复杂度也大大增加。在这个背景下,平台的技术架构也完成了从传统的单体应用到微...

老道士
今天
1
0
大数据学习的各个阶段

第一阶段:Linux课程讲解Linux基础操作,讲的是在命令行下进行文件系统的操作,这是Hadoop学习的基础,后面的所有视频都是基于linux操作的。鉴于很多学员没有linux基础,特增加该内容,保证零linux...

董黎明
今天
0
0
CVE-2013-0077 堆溢出分析

找了很久才发现这个环境比较容易搭建分析... 环境: 系统---Win XP SP3 漏洞程序:QQPlayer 3.7.892.400 出错DLL:quartz.dll 6.5.2600.5512 调试工具:x32db+gflag.exe 过程: 首先gflag设置...

Explorer0
今天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部