Java中join()方法原理及使用教程
简介
前面几篇文章讲解了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()
方法。
更多推荐
所有评论(0)