Thread 类的 join 方法
今天看到一个问题
如何确保 main 方法所在的线程是 Java 程序最后结束的线程
答案是使用 join 方法
于是去了解了一下这个方法
join 的作用
引言
跟刚才我看到的问题类似, 一个面试题:
Java 中如何让多线程按照自己指定的顺序执行?
最简单的回答就是通过 Thread.join 来实现
直接上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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 { previousThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep((long) Math.random() * 10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("num:" + i); }
public static void main(String[] args) { Thread previousThread = Thread.currentThread(); for (int i = 0; i < 10; i++) { JoinDemo joinDemo = new JoinDemo(previousThread, i); joinDemo.start(); previousThread = joinDemo; } } }
|
代码中,每个线程会随机 sleep 一段时间, 如果将previousThread.join()
注释掉,结束顺序将会随机
如果不注释,可以看到结果为 0 到 9 依次排列
所以 join 对于线程的作用就是阻塞,等待线程中止后返回.
注意阻塞这一点,如果线程一直不结束,将会一直阻塞
注意: 必须在 start 之后调用 join 才有意义
join 原理
看一下源码来理解一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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) { while (isAlive()) { wait(0); } } else { while (isAlive()) { long delay = millis - now; if (delay <= 0) { break; } wait(delay); now = System.currentTimeMillis() - base; } } }
|
可以看到, 核心是调用 wait 方法(Object 类的)
由于 wait 方法必须要获取锁, 所以 join 方法被 synchronized 修饰, 在方法层面, 相当于 synchroinzed(this), 是本身的实例
线程执行完毕唤醒主线程的原因
对于线程来说, wait 方法阻塞之后需要 notify/notifyall 来唤醒.那么这个 join 最后是怎么唤醒主线程的? 是因为底层的源码在线程执行完毕后有一个唤醒操作
join 的使用场景
适用于我们需要等待线程执行结果来进行后续处理的场合
参考资料:
- https://zhuanlan.zhihu.com/p/53078651
- https://www.cnblogs.com/duanxz/p/5038471.html
- https://www.journaldev.com/1024/java-thread-join-example