简介

前面几篇文章讲解了wait()方法之后,我们再来讲讲join()方法,因为join()方法就是通过wait()方法实现的。

一.join()方法的作用

作用:让主线程等待(WAITING状态),一直等到其他线程不再活动为止。

join在英语中是“加入”的意思,join()方法要做的事就是,当有新的线程加入时,主线程会进入等待状态,一直到调用join()方法的线程执行结束为止。

join用法示例:
ublic static void main(String[] args) {
    Runnable runnable = new Runnable() {
			@Override
			public void run() {
				System.out.println("子线程执行");
			}
		};
    Thread thread1 = new Thread(runnable);
    Thread thread2 = new Thread(runnable);
    thread1.start();
    thread2.start();
    try {
        //主线程开始等待子线程thread1,thread2
        thread1.join();
        thread2.join();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //等待两个线程都执行完(不活动)了,才执行下行打印
  `  System.out.println("执行完毕")`;;
}

执行分析:代码会在thread1和thread2执行完后,才会执行System.out.println("执行完毕");

join()方法的第一种等效写法

其中,这两行代码

thread1.join();
thread2.join();

可以替换为:

while (thread1.isAlive() || thread2.isAlive()) {
    //只要两个线程中有任何一个线程还在活动,主线程就不会往下执行
}

这两种写法作用一样。

二.join源码分析

join()方法代码是通过java代码实现的,代码如下:

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) {
        // 关键实现在此行,通过wait方法永久等待。
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

join()方法的两种等效写法

源码中:

 while (isAlive()) {
            wait(0);
        }

这就是为什么线程会一直等待,因为它调用的就是wait()方法呀。

关于wait()方法的介绍,请看《Java多线程wait()和notify()系列方法使用教程(内涵故事)》,此篇文章有详细讲解。

join()方法的第二种等效写法

所以join()方法又等同于以下代码:

synchronized(thread1)){
    thread1.wait();
}

源码中的疑问?

join()方法是用wait()方法实现,但为什么没有通过notify()系列方法唤醒呀,如果不唤醒,那不就一直等待下去了吗?

原因是:在java中,Thread类线程执行完run()方法后,一定会自动执行notifyAll()方法

详细参考文章《Java多线程中notifyAll()方法使用教程》中的第三部分“三.关于notifyAll()非常重要的隐藏知识点”,这篇文章写了详细解释。

三.使用join方法时,如何处理中断

当主线程中断时,最稳妥的方式是让子线程也中断(传递中断),这样就维护了线程间数据的一致性。

我们在子线程中中断主线程,然后当主线程执行thread1.join()方法时,就会抛出异常,异常原因是主线程已经被中断了,所以此行代码不能继续执行了。此时,最优秀的做法是把主线程的中断状态传递给子线程,即在catch()语句中,执行thread1.interrupt(),这样此线程也会中断,维护了多线程之间的数据一致性。

代码演示:
public static void main(String[] args) {
		// 获取主线程
		Thread mainThread = Thread.currentThread();
		Thread thread1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					// 在子线程中,让主线程中断
					System.out.println("thread1已启动,在thread1的run方法中,让主线程中断停止了");
					mainThread.interrupt();
					TimeUnit.SECONDS.sleep(4);
					System.out.println("thread1的run方法运行结束");
				} catch (InterruptedException e) {
					System.out.println("子线程开始响应中断,抛出中断异常说明成功中断");
				}
			}
		}, "thread1");
		thread1.start();
		try {
			// 主线程在等待子线程执行结束后,再执行后续代码。如果主线程在等待时被打断,那thread1.join()会抛出异常,
			// 此时正确的做法是在catch语句中将中断传递给thread1,让thread1也中断,保证多个线程执行的一致性;
			// 若不手动终止thread1,则thread1会继续执行,可能会造成一些数据同步上的问题。
			thread1.join();
		} catch (InterruptedException e) {
			System.out.println("主线程执行thread.join()方法时出现异常,提示主线程中断了(验证线程名:" + Thread.currentThread().getName() + ")");
			e.printStackTrace();
			thread1.interrupt();
		}
		System.out.println("main方法全部运行完毕");
	}

总结

本节介绍了join()方法的使用方法,分析了join()方法的源码,并且找到了两种join()方法的等效替换方法,然后我们讲解了使用join()方法时,讲解了为什么要响应主线程的中断,维护好多线程的数据一致性,并给出了代码示例。希望通过本文,可以彻底吃透join()方法。

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐