阅读JDK Join方法注释如下:
Waits for this thread to die.//等待该线程死亡
所以其作用是 等待该线程死亡。简单理解就是,调用该方法的线程阻塞,直到被调用Join的线程死亡。
我们回忆一下,线程的5种状态,如下:
等待该线程死亡从状态角度看,就是等待其他状态转入死亡状态。
我们先展示一个基本的例子,主线程等待子线程完成后执行。
public static void main(String[] args) { Thread thread1 = new Thread(new Runnable() { public void run() { System.out.println("子线程执行开始"); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("子线程执行结束"); } }); try { System.out.println("主线程执行开始"); thread1.start(); thread1.join(); System.out.println("主线程执行完成"); } catch (InterruptedException e) { e.printStackTrace(); } }
我们先构造T1、T2、T3线程,让它们分别等待6s、4s、2s。如果没有Join,执行完成顺序是T3、T2、T1,存在Join,则完成顺序为T1、T2、T3。
public static void main(String[] args) { final Thread t1 = new Thread(new Runnable() { public void run() { System.out.println("T1子线程执行开始"); try { //设置6秒,让T1最后执行完 Thread.sleep(6000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1子线程执行结束"); } }); final Thread t2 = new Thread(new Runnable() { public void run() { System.out.println("T2子线程执行开始"); try { t1.join(); //设置4秒 Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T2子线程执行结束"); } }); Thread t3 = new Thread(new Runnable() { public void run() { System.out.println("T3子线程执行开始"); try { t2.join(); //设置2秒 Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T3子线程执行结束"); } }); t1.start(); t2.start(); t3.start(); }
Object.join()首先调用join(long millis)。代码如下:
public final void join() throws InterruptedException { join(0);//直接调用重载方法,入参为0,也就是等待最大时间为0,持续阻塞 }
下面是join的核心代码,我们在方法中逐一讲解
public final synchronized void join(long millis) throws InterruptedException { long base = System.currentTimeMillis();//获得初入方法时间 long now = 0;//设置已经执行的时间,初始化为0 if (millis < 0) {//验证参数合法性,小于0不合法 throw new IllegalArgumentException("timeout value is negative"); } if (millis == 0) {//等于0,说明无最大超时时间 while (isAlive()) {//检查线程状态是否死亡,如果非死亡,则阻塞 wait(0);//wait(0)等同于wait(),会一直阻塞。直到线程死亡时,会调用notifyAll()通知所有阻塞线程 } } else { while (isAlive()) {检查线程状态是否死亡,如果非死亡,则执行 long delay = millis - now; //计算剩余等待时间,通过最大等待时间-已经执行的时间 if (delay <= 0) {//到达最大等待时间,则退出 break; } wait(delay);//等待 剩余时长 now = System.currentTimeMillis() - base;//每次循环执行,都会当前执行时间-初入方法时间,得到已经执行的时间 } } }
可以看到其核心就是检查Thread是否死亡状态,并通过wait()方法持续阻塞,直到线程死亡时,触发notifyAll。所以join的阻塞实际上也是通过native wait()方法实现的。
思考一
学习上面知识后,思考wait(0)即可实现一直等待,直到线程死亡,为什么外层需要while检查状态,而不使用if?
回答:因为目标Thread对象可能被其他线程持有,它们有可能调用notify()、notifyAll()接口,如果未使用while,会导致wait(0)被唤醒,join继续执行,而目标Thread仍未死亡。