【多线程】学习记录七种主线程等待子线程结束之后在执行的方法
最近遇到一个问题需要主线程等待所有的子线程结束,才能开始执行,统计所有的子线程执行结果,返回,网上翻阅各种资料,最后记录一下,找到七种方案
第一种: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);}
【多线程】学习记录七种主线程等待子线程结束之后在执行的方法相关推荐
- Java多线程之----主线程会等待子线程结束再结束么,怎么让主线程等待子线程结束呐?
首先给出结论: 主线程和子线程之间没有谁先谁后结束这种关联,它们只是各自负责自己的线程任务,如果该线程的任务结束了,该线程自然会结束运行. talk is cheap,show me the code ...
- c++主线程等待子线程结束_简单明了的 Python 多线程来了 | 原力计划
作者 | 万里羊责编 | 王晓曼出品 | CSDN博客线程和进程计算机的核心是CPU,它承担了所有的计算任务,就像是一座工厂在时刻运行.如果工厂的资源有限,一次只能供一个车间来使用,也就是说当一个车间 ...
- VC++ 中主线程等待子线程结束的方法
void WaitForThreadExit(void) {DWORD dwRet; //返回值MSG msg; int wait_count=4; //线程句柄有4个int nExitThreadC ...
- Java多线程协作CountDownLatch,主线程等待子线程结束
CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 主要方法 public CountDownLatch(int count);构造方 ...
- java 主线程等待_Java实现主线程等待子线程
本文介绍两种主线程等待子线程的实现方式,以5个子线程来说明: 1.使用Thread的join()方法,join()方法会阻塞主线程继续向下执行. 2.使用Java.util.concurrent中的C ...
- 如何实现java主线程等待子线程执行完毕之后再执行?
本文转自:问题:如何实现java主线程等待子线程执行完毕之后再执行? - jseven - 博客园 点击关注强哥,查看更多精彩文章呀 工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完 ...
- Java并发编程原理与实战六:主线程等待子线程解决方案
Java并发编程原理与实战六:主线程等待子线程解决方案 参考文章: (1)Java并发编程原理与实战六:主线程等待子线程解决方案 (2)https://www.cnblogs.com/pony1223 ...
- java等待5秒_Java并发编程-主线程等待子线程解决方案
主线程等待所有子线程执行完成之后,再继续往下执行的解决方案 public class TestThread extends Thread { public void run() { System.ou ...
- Java主线程等待子线程、线程池
public class TestThread extends Thread { public void run() { System.out.println(this.getName() + &qu ...
最新文章
- php使用NuSoap产生webservice结合WSDL让asp.net调用
- xadmin后台页面定制和添加服务器监控组件
- Xamarin.Android编译提示找不到mscorlib.dll.so文件
- 汇编中的通用寄存器、标志寄存器、段寄存器
- 个人工作日报模板_2020最新销售店长个人年度工作计划模板精选3篇
- Codeforces Round #654 (Div. 2)
- 【JavaScript】编写一个炫彩的网页时钟
- jmeter tps指标在哪里看_性能之路——性能测试连载 (3)-性能指标
- PairSCL:句子对级别的有监督对比学习方法
- linux apache访问日志,linux分析apache日志获取最多访问的前10个IP
- 自己攒的正则表达式---判断汉字、字符但不要数字
- 如何在Axure使用iconfont图标库里的图标
- 学生管理-axios优化
- 计算机关机慢怎么解决方法,电脑关机很慢,详细教您win7电脑关机很慢的解决方法...
- 产品思维训练 | 你的项目总是不能按期上线,你会如何解决?
- 视频编码中CBR和VBR的区别
- jQuery根据ID删除元素
- React-Redux使用方法
- 韩国媒体:中国手机的崛起,都是依靠“性价比”?
- 顶级智囊支招 丰泽智慧城市建设