Java多线程之以7种方式让主线程等待子线程结束
记一次主线程等待子线程结束的多种方法的学习
在学习多线程时,最开始遇到的问题其实是“计算子线程运行时间”,写到最后发现本文和标题更为符合,但是仍然基于问题:“在主线程中获取子线程的运行时间”。
while循环
对于“主线程如何获取子线程总运行时间”的问题,最开始想到的是使用while循环进行轮询:
Thread t = new Thread(() -> {
//子线程进行字符串连接操作
int num = 1000;
String s = "";
for (int i = 0; i < num; i++) {
s += "Java";
}
System.out.println("t Over");
});
//开始计时
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
long end = 0;
while(t.isAlive() == true){//t.getState() != State.TERMINATED这两种判断方式都可以
end = System.currentTimeMillis();
}
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
但是这样太消耗CPU,所以我在while循环里加入了暂停:
while(t.isAlive() == true){
end = System.currentTimeMillis();
try {
Thread.sleep(10);
}catch (InterruptedException e){
e.printStackTrace();
}
}
这样做的结果虽然cpu消耗减少,但是数据不准确了
Thread的join()方法
接着我又找到了第二种方法:
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t1.start();
try {
t.join();//注意这里
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - Start:" + (end - start));
使用join()方法,join()方法的作用,是等待这个线程结束;(t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续,这里贴个说的挺清楚的博客)
synchronized的等待唤醒机制
第二种方法的确实现了计时,接着我又想到了多线程的等待唤醒机制,思路是:子线程启动后主线程等待,子线程结束后唤醒主线程。于是有了下面的代码:
Object lock = new Object();
Thread t = new Thread(() -> {
int num = 1000;
String s = "";
for (int i = 0; i < num; i++) {
s += "Java";
}
System.out.println("t Over");
lock.notify();//子线程唤醒
});
//计时
long start = System.currentTimeMillis();
System.out.println("start = " + start);
//启动子线程
t.start();
try {
lock.wait();//主线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
但是这样会抛出两个异常:
由于对wait()和notify()的理解并不是很深刻,所以我最开始并不清楚为什么会出现这样的结果,因为从报错顺序来看子线程并没有提前唤醒,于是我在segmentfault和CSDN都发出了提问,同时也询问了我一个很厉害的朋友,最后得出的结论是调用wait()方法时需要获取该对象的锁,Object文档里是这么说的:
The current thread must own this object's monitor.
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
所以上面的代码需要改成这样:
Thread t = new Thread(() -> {
int num = 1000;
String s = "";
for (int i = 0; i < num; i++) {
s += "Java";
}
System.out.println("t Over");
synchronized (lock) {//获取对象锁
lock.notify();//子线程唤醒
}
});
//计时
long start = System.currentTimeMillis();
System.out.println("start = " + start);
//启动子线程
t.start();
try {
synchronized (lock) {//这里也是一样
lock.wait();//主线程等待
}
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
这样的确得出了结果,但是我想知道两个线程的执行顺序,于是在wait和nitify前后分别加了一个输出,最后得出的运行结果是:
可以看出主线程先wait子线程再notify,也就是说,如果子线程在主线程wati前调用了nitify,会导致主线程无限等待,所以这个思路还是有一定的漏洞的。
关于wait和notify这里贴个挺清楚的博客
CountDownLatch
第四种方式可以等待多个线程结束,就是使用java.util.concurrent包下的CountDownLatch类(关于CountDownLatch的用法可以参考这篇简洁的博客)
简单来说,CountDownLatch类是一个计数器,可以设置初始线程数(设置后不能改变),在子线程结束时调用countDown()方法可以使线程数减一,最终为0的时候,调用CountDownLatch的成员方法wait()的线程就会取消BLOKED阻塞状态,进入RUNNABLE从而继续执行。下面上代码:
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";
}
System.out.println("t Over");
cdl.countDown();//此方法是CountDownLatch的线程数-1
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
//线程启动后调用countDownLatch方法
try {
cdl.await();//需要捕获异常,当其中线程数为0时这里才会继续运行
}catch (InterruptedException e){
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
Future
又想到刚学习了线程池,线程池的submit()的返回对象Future接口有一个get()方法也可以阻塞当前线程(其实该方法主要用途是获取子线程的返回值),所以第五种方法也出来了:
ExecutorService executorService = Executors.newFixedThreadPool(1);
Thread t = new Thread(() -> {
int num = 1000;
String s = "";
for (int i = 0; i < num; i++) {
s += "Java";
}
System.out.println("t Over");
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
Future future = executorService.submit(t);//子线程启动
try {
future.get();//需要捕获两种异常
}catch (InterruptedException e){
e.printStackTrace();
}catch (ExecutionException e){
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
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接口.参见这篇博客
看到这个Callable突然想到之前看C#多线程的时候有说到回调的问题,因此先开个坑,下篇博文说说Java的Callable与callback问题,先贴个Callable的简单讲解
BlockingQueue
同时,在concurrent包中,还提供了BlockingQueue(队列)来操作线程,BlockingQueue的主要的用法是在线程间安全有效的传递数据,具体用法可以参见这篇博客,对于BlockingQueue说的非常详细。因此,第六种方法也出来了:
BlockingQueue queue = new ArrayBlockingQueue(1);//数组型队列,长度为1
Thread t = new Thread(() -> {
int num = 1000;
String s = "";
for (int i = 0; i < num; i++) {
s += "Java";
}
System.out.println("t Over");
try {
queue.put("OK");//在队列中加入数据
} catch (InterruptedException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
try {
queue.take();//主线程在队列中获取数据,take()方法会阻塞队列,ps还有不会阻塞的方法
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
CyclicBarrier
那么,有没有第七种方式呢?当然有啦~,还是concurrent包,只不过这次试用CyclicBarrier类:
CyclicBarrier字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。
CyclicBarrier barrier = new CyclicBarrier(2);//参数为线程数
Thread t = new Thread(() -> {
int num = 1000;
String s = "";
for (int i = 0; i < num; i++) {
s += "Java";
}
System.out.println("t Over");
try {
barrier.await();//阻塞
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
try {
barrier.await();//也阻塞,并且当阻塞数量达到指定数目时同时释放
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
实际是上面这种方法是不太严谨的,因为在子线程阻塞之后如果还有代码是会继续执行的,当然本例中后面是没有代码可执行了,可以近似理解为是子线程的运行时间。
这里贴个CountDownLatch、CyclicBarrier和Semaphore的讲解博客
小结
至此,集齐了七颗龙珠,得出小结:
- while循环进行轮询
- Thread类的join方法
- synchronized锁
- CountDownLatch
- Future
- BlockingQueue
- CyclicBarrier
Java多线程之以7种方式让主线程等待子线程结束的更多相关文章
- Java并发编程原理与实战六:主线程等待子线程解决方案
本文将研究的是主线程等待所有子线程执行完成之后再继续往下执行的解决方案 public class TestThread extends Thread { public void run() { Sys ...
- Java 并发编程中的 CountDownLatch 锁用于多个线程同时开始运行或主线程等待子线程结束
Java 5 开始引入的 Concurrent 并发软件包里面的 CountDownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是 ...
- 线程:Java主线程等待子线程结束
使用Thread.join()方法: public class App { public static void main(String[] args) { testMain(); } public ...
- JAVA进阶----主线程等待子线程各种方案比较(转)
创建线程以及管理线程池基本理解 参考原文链接:http://www.oschina.net/question/12_11255?sort=time 一.创建一个简单的java线程 在 Java 语言中 ...
- java多线程实现主线程等待子线程执行完问题
本文介绍两种主线程等待子线程的实现方式,以5个子线程来说明: 1.使用Thread的join()方法,join()方法会阻塞主线程继续向下执行. 2.使用Java.util.concurrent中的C ...
- Java实现主线程等待子线程
本文介绍两种主线程等待子线程的实现方式,以5个子线程来说明: 1.使用Thread的join()方法,join()方法会阻塞主线程继续向下执行. 2.使用Java.util.concurrent中的C ...
- Java多线程--让主线程等待子线程执行完毕
使用Java多线程编程时经常遇到主线程需要等待子线程执行完成以后才能继续执行,那么接下来介绍一种简单的方式使主线程等待. java.util.concurrent.CountDownLatch 使用c ...
- Java主线程等待子线程、线程池
public class TestThread extends Thread { public void run() { System.out.println(this.getName() + &qu ...
- Java线程池主线程等待子线程执行完成
今天讨论一个入门级的话题, 不然没东西更新对不起空间和域名~~ 工作总往往会遇到异步去执行某段逻辑, 然后先处理其他事情, 处理完后再把那段逻辑的处理结果进行汇总的产景, 这时候就需要使用线程了. 一 ...
随机推荐
- FFmpeg RTSP流通过UDP传输问题
我自己在使用SRS服务的Ingest功能时发现在读取一个网络摄像头的RTSP流时一直不成功, 具体分析后发现SRS在调用FFmpeg时出了问题: /usr/local/ffmpeg/bin/ffmpe ...
- Spring Mvc中Jsp也页面怎么会获取不到Controller中的数据
----------Controller ------- package com.test.mvc; import org.springframework.stereotype.Controller; ...
- 用Navicat连接阿里云ECS服务器上的MySQL数据库,连接不上,并且报10060错误
设置远程访问(使用root密码): grant all privileges on . to 'root' @'%' identified by '123456'; flush privileges; ...
- js中每隔一段时间执行一次
window.setInterval("flushs()",1000);
- iOS项目Info.plist中关键字汇总
1. Application does not run in background (键名:UIApplicationExistsOnSuspend) 自从iOS4.0之后,当你在应用程序执行的时候按 ...
- 【快学springboot】12.实现拦截器
前言 之前在[快学springboot]6.WebMvcConfigurer配置静态资源和解决跨域里有用到WebMvcConfigurer接口来实现静态资源的映射和解决跨域请求,并且在文末还说了Web ...
- No module named 'widgets'
https://blog.csdn.net/heatdeath/article/details/70313645 适配python3的. https://github.com/twz915/Djang ...
- Vue 路由组件
目录 组件数据局部化处理 路由逻辑跳转 组件传参 父传子 子传父 组件的生命周期钩子 路由传参 全家配置自定义 CSS 与 js 总结: 组件数据局部化处理 不管页面组件还是小组件,都可能会被多次复用 ...
- Windows驱动开发-IoCompleteRequest
IoCompleteRequest 例程表示调用者的已经完成了对指定I/O请求的所有处理操作,并且向I/O管理器返回指定的IRP报文. //函数原型 VOID IoCompleteRequest( I ...
- ZCGL大数据平台性能优化
对HBase数据库建索引 参见:基于Phoenix对HBase建索引 https://www.cnblogs.com/ratels/p/11203313.html 将HBase访问微服务整合到模块 ...