Java中如何让多线程按照自己指定的顺序执行
摘要:基于如何让多线程按照自己指定的顺序执行这个场景,浅谈Thread中join()函数的作用和原理。
join的作用
之前有人问过我一个这样的面试题:如何让多线程按照自己指定的顺序执行?这个问题最简单的回答是通过Thread.join来实现。
让父线程等待子线程结束之后才能继续运行。我们来看看在 Java 7 Concurrency Cookbook 中相关的描述(很清楚地说明了 join() 的作用):
Waiting for the finalization of a threadIn some situations, we will have to wait for the finalization of a thread. For example, we may have a program that will begin initializing the resources it needs before proceeding with the rest of the execution. We can run the initialization tasks as threads and wait for its finalization before continuing with the rest of the program. For this purpose, we can use the join() method of the Thread class. When we call this method using a thread object, it suspends the execution of the calling thread until the object called finishes its execution.
黑体部分英文的大意是当我们用某个线程调用这个方法时,join方法会挂起调用线程,直到被调用线程结束执行,调用线程才会继续执行。下面用一个示例验证一下:
public class JoinDemo extends Thread {
int i;
Thread previousThread; //上一个线程
public JoinDemo(Thread previousThread, int i) {
this.previousThread = previousThread;
this.i = i;
}
@Override
public void run() {
try {
//调用上一个线程的join方法,自己演示的时候可以把这行代码注释掉
previousThread.join();
Object aa = new Object();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(previousThread.getName() + ", num:" + i);
}
public static void main(String[] args) {
Thread previousThread = Thread.currentThread();
previousThread.setName("parent thread");
for (int i = 0; i < 10; i++) {
JoinDemo joinDemo = new JoinDemo(previousThread, i);
joinDemo.start();
previousThread = joinDemo;
previousThread.setName("child thread " + i);
}
}
}
上面的代码,注意 previousThread.join()部分,大家可以把这行代码注释以后看看运行效果,在没有加join的时候运行的结果是不确定的,加了join以后运行结果按照递增的顺序输出。
源码分析
public class Thread implements Runnable {
...
public final void join() throws InterruptedException {
join(0);
}
...
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) { //判断是否携带阻塞的超时时间,等于0表示没有设置超时时间
while (isAlive()) {//isAlive获取线程状态,无线等待直到previousThread线程结束
wait(0); //调用Object中的wait方法实现线程的阻塞
}
} else { //阻塞直到超时
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
...
由此可见,join()方法是通过调用Object中的wait()函数实现线程的阻塞。main线程在调用previousThread.join()时,会持有线程对象previousThread的锁(wait 意味着拿到该对象的锁),然后调用previousThread的wait方法造成主线程阻塞,直到该对象唤醒main线程(即子线程previousThread执行完毕退出的时候)。synchronized修饰在方法层面相当于synchronized(this),this就是previousThread本身的实例。
子线程结束后,子线程previousThread的this.notifyAll()会被调用,join()返回,主线程只要获取到锁和CPU执行权,就可以继续执行了。
小结
首先join() 是一个synchronized方法, 里面调用了wait(),这个过程的目的是让持有这个同步锁的线程进入等待,那么谁持有了这个同步锁呢?答案是主线程,因为主线程调用了previousThread.join()方法,相当于在previousThread.join()代码这块写了一个同步代码块,谁去执行了这段代码呢?是主线程,所以主线程被wait()了。然后在子线程previousThread执行完毕之后,JVM会调用lock.notify_all(thread);唤醒持有previousThread这个对象锁的线程,也就是主线程,使主线程继续执行。
Thread.join使用场景
在实际使用过程中,我们可以通过join方法来等待线程执行的结果,其实有点类似future/callable的功能。通过以下伪代码来说明join的使用场景:
public void joinDemo(){
//....
Thread t=new Thread(payService);
t.start();
//....
//其它业务逻辑处理,不需要确定t线程是否执行完
insertData();
//后续的处理,需要依赖t线程的执行结果,可以在这里调用join方法等待t线程的执行结束
t.join();
}
Reference
- https://www.cnblogs.com/aademeng/articles/10882468.html
- https://www.jianshu.com/p/fc51be7e5bc0
- https://www.cnblogs.com/huangzejun/p/7908898.html
Java中如何让多线程按照自己指定的顺序执行的更多相关文章
- 在java中怎样实现多线程?线程的4种状态
一.在java中怎样实现多线程? extends Thread implement Runnable 方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 ...
- Java 中 try、catch、finally 语句块的执行顺序
假设代码顺序书写如下:try → catch → finally → 其他代码 则: 1.正常执行顺序:try → catch → finally → 其他代码 2.try,catch和finally ...
- Java中的线程--多线程面试题
到这里,基本上线程的并发中的知识点都是学到了,到了最后,还有三道面试题,从面试题中学习更加的加深一下,多线程中的知识点,如何在实际的问题中来解决多线程的问题,可以更好的从实际出发 一.面试题1 面试题 ...
- Java中,对多线程访问同一变量(并发访问)的认识
在Java中,如果启动多个线程对同一个对象或者变量时候,在没有安全保护前提下有可能会抛出并异常 java.util.ConcurrentModificationException 当方法检测到对象的并 ...
- Java中如何判断当前环境是大端字节顺序还是小端字节顺序
Java非字节类型的基本类型,除了布尔型都是由组合在一起的几个字节组成的.这些数据类 型及其大小总结在表 2-1 中. 表:基本数据类型及其大小 数据类型 大小(以字节表示) Byte 1 Char ...
- Java中try、finally语句中有return时的执行情况 [转]
原文:http://kingj.iteye.com/blog/1436761 在Java中当try.finally语句中包含return语句时,执行情况到底是怎样的,finally中的代码是否执行,大 ...
- JAVA中GC时finalize()方法是不是一定会被执行?
在回答上面问题之前,我们一定要了解JVM在进行垃圾回收时的机制,首先: 一.可达性算法 要知道对象什么时候死亡,我们需要先知道JVM的GC是如何判断对象是可以回收的.JAVA是通过可达性算法来来判断 ...
- JAVA中try、catch、finally带return的执行顺序总结
异常处理中,try.catch.finally的执行顺序,大家都知道是按顺序执行的.即,如果try中没有异常,则顺序为try→finally,如果try中有异常,则顺序为try→catch→final ...
- java中HashMap在多线程环境下引起CPU100%的问题解决(转)
最近项目中出现了Tomcat占用CPU100%的情况,原以为是代码中出现死循环,后台使用jstack做了dump,发现是系统中不合理使用HashMap导致出现了死循环(注意不是死锁). 产生这个死循环 ...
- Java面试题:Java中怎么样实现多线程
方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 run() ,加入线程所要执行的代码即可. 下面是一个例子: public class MyThrea ...
随机推荐
- Oracle 23ai TPC-H 测试环境部署
最近,我在 Oracle Database 23ai 上进行了 TPC-H 100GB 测试,并整理了完整的实施步骤和优化经验.如果你也想评估 Oracle 数据库在决策支持场景下的性能,可以参考我的 ...
- 百万架构师第四十八课:并发编程的原理(三)|JavaGuide
原文链接 JavaGuide 并发编程的原理 目标: Lock 的使用 AQS 原理分析 Condition CountDownLatch . Semaphore 线程池分析 J.U.C = java ...
- angular项目中修改nz-zorro组件库字体大小
有时候我们开发时使用到的组件库,可能样式不是符合我们的需求,我试着从谷歌调试工具获取组件的类,给他设置样式,如下我设置tabset的样式 .ant-tabs-nav .ant-tabs-tab { f ...
- How to use the shell, terminal and the advanced tools
How to use the shell, terminal and the advanced tools Introduction Why use English instead of Chin ...
- vue强制刷新页面
方法一 this.$router.go(0) // 会出现一段空白页,用户体验不好 方法二 在 app.vue 中定义 reload() 方法 <template> <div id= ...
- oracle修改用户密码的方法
Oracle用户名及默认密码 修改oracle用户的密码有以下方法: 普通用户 (1)通过alter user语法来进行修改 ,这也是最常见的方式: (2) 第二种方式,是通过password命令来修 ...
- NodeJS运行时抛出: Error: listen EADDRINUSE :::3000
错误详情Error: listen EADDRINUSE :::3000 at Server.setupListenHandle [as _listen2] (net.js:1360:14) ...
- 一个专业DBA应具备的技能
本文可以作为MySQL DBA面试官,以及候选人的双向参考 面试流程 接下来先说下我以往在做MySQL DBA面试时的过程(套路): 1.先自我介绍后,再让候选人花2-5分钟做下自我简介有不少人可能对 ...
- ESP32+Arduino入门(三):连接WIFI获取当前时间
ESP32内置了WIFI模块连接WIFI非常简单方便. 代码如下: #include <WiFi.h> const char* ssid = "WIFI名称"; con ...
- HashMap 批量添加
需要初始化一个常量HashMap,并希望在一行语句中完成.避免像这样的事情: hashMap.put("One", new Integer(1)); // adding value ...