背景
在使用多线程操作时,有这样的一个操作非常常见:当我们遇到一个任务比较繁重时,我们会分配一个子线程 t 去执行,从而使主线程仍然继续处理当前事务。然而当主线程处理好好其他事务后,发现子线程仍然没有结束,于是主线程就需要等待子线程执行完成。
问题
这里就会有这个的一个问题:主线程如何知道子线程执行已经结束了?一些对Java不太了解的初级程序员通常的作法就是在主线程中使用一个循环去检测子线程的状态,代码大概如下:
// 错误示例1
while(t.getState() == java.lang.Thread.State.RUNNABLE)
continue;
// 错误示例2: 在线程中建立一个变量 isRunning,用于表示线程是否运行
// 在执行开始前将 isRunning 设置为 true,完成后设置为 false
while(t.isRunning)
continue;
这两种写法不仅多余,而且性能很差。因为这种主动检测的循环每秒会被执行数亿次,把一个CPU的核心会完全占满。如果真要这么写,中间也要加上 Thread.sleep(1);
从而将循环次数减少至1000次每秒。
使用Join解决问题
为了解决这个问题,Java为Thread类提供了一个非常好用的方法: join()。请看下面的示例:
public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
System.out.println("Thread-0 Exited.");
});
System.out.println("main Invokes b.");
t.start();
// t.join();
System.out.println("main Exited.");
}
在以上的代码中, 我们首先定义了一个线程t。为了简化问题,我们只让它执行了一行输出代码,然后主线程输出 main Invokes b.
,再启动 t,最后输出 main Exited.
。执行后,输出如下所示:
main Invokes b.
main Exited.
Thread-0 Exited.
根据这个输出,我们可以用下面的图来理解。
在主程序中,所有的内容在t时刻都完成了,由于线程启动是需要时间的,所以线程 t 在 t 时刻才启动完成,然后输出 Thread-0 Exited.
并在 t
让我们把代码中注释掉的一行去掉,再一次执行代码,即可获得以下的输出内容:
main Invokes b.
Thread-0 Exited.
main Exited.
这个过程,我们可以结合下面一张图来理解。
如图所示,主线程被明显延长了,在调用了 t.joint()
以后,就一直处于等待状态,直到 t 完全执行完,即输出 Thread-0 Exited
以后,才结束对 t.joint()
的调用,然后输出最后一行。
扩展
以上的示例中,只使用了一个子线程,如果使用多个子线程,同时需要等待所有子线程完成任务,也是一样可行,示例代码如下:
public static void main(String[] args) throws Exception {
// 这里启动了10个线程
Thread[] ts = new Thread[10];
for (int i = 0; i < ts.length; i++) {
int fi = i;
ts[i] = new Thread(() -> {
System.out.println("Thread-" + fi + " Exited.");
});
}
// 调用10个线程
System.out.println("main Invokes ts.");
for (Thread t : ts)
t.start();
// 这里让10个线程都进行join
for (Thread t : ts)
t.join();
// 最后输出主线程完成
System.out.println("main Exited.");
}
结论
通过对以上代码分析,我们可以知道,join()
会让调用线程等待被调用线程结束后,才会继续执行。使用的场景为我们需要等待某个线程执行完成后才可继续执行的场景。