鉴于 网上许多博客对于 join()方法 只是从底层源码分析看了个大概,我没能真正理解。但直到看完这三篇文章,有一种豁然开朗的感觉。所以自己在前辈的基础上总结一下 join()方法。
文1:https://blog.csdn.net/lyzx_in_csdn/article/details/79676708
文2:https://blog.csdn.net/chenkaibsw/article/details/80912878
文3(豁然开朗):https://blog.csdn.net/u013425438/article/details/80205693#commentsedit
join方法的真正含义:
在线程A中 调用 线程B对象.join( )方法:1. 如果线程B 是可运行态,则线程A阻塞自己运行,直到线程B 运行结束;2. 如果线程B 不是可运行状态,则继续执行线程A,并不会执行线程B.。
通过代码去感受
实验1
//下面自己定义两个线程。
public class Thread1 extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<11;i++) {
System.out.println(Thread.currentThread().getName() + "------" + i);
}
}
}
public class Thread2 implements Runnable {
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<11;i++) {
System.out.println(Thread.currentThread().getName() +"----- " + i);
}
}
}
//测试类
public class JoinTest {
public static void main(String[] args) {
//给t1 t2线程取名字 方便对于执行结果的观察
Thread t1 = new Thread(new Thread1(), "Thread---t1--");
Thread t2 = new Thread(new Thread2(), "Thread---t2--");
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
运行结果:
执行多次后,运行结果均为
Thread---t1--------0
Thread---t1--------1
Thread---t1--------2
Thread---t1--------3
Thread---t1--------4
Thread---t1--------5
Thread---t1--------6
Thread---t1--------7
Thread---t1--------8
Thread---t1--------9
Thread---t1--------10
Thread---t2------- 0
Thread---t2------- 1
Thread---t2------- 2
Thread---t2------- 3
Thread---t2------- 4
Thread---t2------- 5
Thread---t2------- 6
Thread---t2------- 7
Thread---t2------- 8
Thread---t2------- 9
Thread---t2------- 10
实验2
//Thread1 Thread2 代码并没有改变 省略不写
public class JoinTest {
public static void main(String[] args) {
Thread t1 = new Thread(new Thread1(), "Thread---t1--");
Thread t2 = new Thread(new Thread2(), "Thread---t2--");
t1.start();
t2.start();
try {
t1.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
运行结果:
执行多次,发现 t1线程 与t2 线程交替执行。
Thread---t1--------0
Thread---t1--------1
Thread---t1--------2
Thread---t2------- 0
Thread---t1--------3
Thread---t2------- 1
Thread---t2------- 2
Thread---t2------- 3
Thread---t2------- 4
Thread---t2------- 5
Thread---t2------- 6
Thread---t2------- 7
Thread---t2------- 8
Thread---t2------- 9
Thread---t2------- 10
Thread---t1--------4
Thread---t1--------5
Thread---t1--------6
Thread---t1--------7
Thread---t1--------8
Thread---t1--------9
Thread---t1--------10
对于实验一 与实验二 运行结果的差异,是由于 t1.join() 加入的位置不同。
实验1 中 ti.join() 加在了 t2.start() 之前,也就是 主线程main 在调用t1线程的 join() 方法时 ,t2线程并没有开启。
而在实验2中 t1.join() 放在了 t2.start() 后面,也就是说 主线程在调用 ti线程的 join()方法之前,已经开启t1 、t2两个线程。
开启后的t2线程将与t1线程、主线程、并行执行。而主线程调用了 t1的join()方法,对已经处于运行状态的t2线程并没有影响,阻塞的是调用t1的join()方法的主线程。在t1线程没有运行结束之前,主线程将一直等待。直到t1线程终止 当线程终止时,t1线程才会调用线程自身的notifyAll()方法,通知所有等待在该线程对象上的线程运行。
运行流程图(当只有主线程 和 t1线程时)
join()的源码:
/**
* Waits for this thread to die.
*
* <p> An invocation of this method behaves in exactly the same
* way as the invocation
*
* <blockquote>
* {@linkplain #join(long) join}{@code (0)}
* </blockquote>
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
public final void join() throws InterruptedException {
join(0); //join()等同于join(0)
}
/**
* Waits at most {@code millis} milliseconds for this thread to
* die. A timeout of {@code 0} means to wait forever.
*
* <p> This implementation uses a loop of {@code this.wait} calls
* conditioned on {@code this.isAlive}. As a thread terminates the
* {@code this.notifyAll} method is invoked. It is recommended that
* applications not use {@code wait}, {@code notify}, or
* {@code notifyAll} on {@code Thread} instances.
*
* @param millis
* the time to wait in milliseconds
*
* @throws IllegalArgumentException
* if the value of {@code millis} is negative
*
* @throws InterruptedException
* if any thread has interrupted the current thread. The
* <i>interrupted status</i> of the current thread is
* cleared when this exception is thrown.
*/
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); //join(0)等同于wait(0),即调用 t1线程的主线程wait无限时间直到被notify
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
其中核心代码如下:
while (isAlive()) { //调用join方法线程是运行时状态
wait(0); //进入等待
}
isAlive()方法下面会做详细的介绍,先看wait(0),wait(0)是什么意思呢,查看下面wait()方法源码,其实wait()方法就是调用了wait(0)方法实现的,wait(0)就是让其一直等待。到这里会发现,其实join方法本质就是利用上面的线程实例作为对象锁的原理,当线程终止时,会调用线程自身的notifyAll()方法,通知所有等待在该线程对象上的线程的运行。
while(isAlive)语句的作用
有下面的一段简单代码:
代码实现:
public class HighConcurrency {
// 1.现在有T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t1线程,等待t1线程执行完
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
// 引用t2线程,等待t2线程执行完
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t3.start();//这里三个线程的启动顺序可以任意,大家可以试下!
t2.start();
}
}
程序输出:
t2
t3
首先执行了t3.start(),在t3线程里的run方法里执行了t2.join(),此时有两种情况,可能还没有执行t2.start(),t2处于初始状态,也有可能执行了t2.start(),t2处于运行时状态,所以看到这里就明白了,join源码中while(isAlive()),其实相当于while(this.isAlive())就相当于判断这里的t2是不是已经是不是运行状态(有没有调用start方法)。这么设计是为了防止t2线程没有运行,此时t3线程如果直接执行wait(0)方法,那么将会一直等待下去,造成代码卡死。
为了验证,代码改为:
t3.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
此时输出
t3
t2
分析:将t2.start()和t3.start()之间的时间间隔变长,这样在t3线程中不会执行t2.join()时,保证了t2时处于初始状态还不是运行状态,此时while(isAlive())不成立,不会执行wait(0)方法,所以t3线程不会等待,会先输出t3。
然后再更改代码如下,保证在执行t2.join()时,t2.start()已经执行,t2已经处于运行状态:
public class HighConcurrency {
public static void main(String[] args) {
final Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2");
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10); //保证此时t2已经是运行时状态了
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t3");
}
});
t3.start();
t2.start();
}
}
此时输出
t2
t3
分析:在t3线程中执行t2.join()方法前先执行了sleep(10)方法,保证在执行t2.join()时,t2已经是运行时状态了,所以此时t3会执行wait(0)方法等待,直到t2先执行完,t3再继续执行,所以输出t2 t3
join()的作用是让“主线程”等待“子线程”结束之后才能继续运行
面试题:
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
package Demo0423JoinTest;
/**
* join<p>
* 面试题
* join()的作用是让“主线程”等待“子线程”结束之后才能继续运行
*
* 现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行
*/
public class JoinTest02 {
public static void main(String[] args) throws InterruptedException {
Thread t3 = new Thread(new MyThread3());
t3.setName("线程3");
t3.start();
t3.join();
System.out.println(Thread.currentThread().getName()+"停止");
}
}
class MyThread1 extends Thread{
@Override
public void run() {
try {
//t1启动
System.out.println(Thread.currentThread().getName()+"启动");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"停止");
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
try {
//启动t1,让t2等待t1执行完成
Thread t1 = new Thread(new MyThread1());
t1.setName("线程1");
t1.start();
t1.join();
//t2启动
System.out.println(Thread.currentThread().getName()+"启动");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"停止");
}
}
}
class MyThread3 extends Thread{
@Override
public void run() {
try {
//启动t2,让t3等待t2执行完成
MyThread2 t2 = new MyThread2();
t2.setName("线程2");
t2.start();
t2.join();
//启动t3
System.out.println(Thread.currentThread().getName()+"启动");
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()+"停止");
}
}
}
运行结果:
线程1启动
线程1停止
线程2启动
线程2停止
线程3启动
线程3停止
main停止