首先详细介绍一下join( )方法的源码
public final synchronized void join(long millis) //参数表示毫秒数
throws InterruptedException {
/*System.currentTimeMillis()方法是调用系统当前的毫秒数,
*日期1970年1月1日00:00:00 GMT之后的毫秒数*/
long base = System.currentTimeMillis();
long now = 0;
//如果传入的参数小于0,跑出异常
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//如果参数等于0时执行下面循环
if (millis == 0) {
/*isAlive():如果当前线程是存活的,返回true,否则返回false
*判断线程是否存活,是的话执行wait(0);*/
while (isAlive()) {
//让当前活着的线程等待,这里的0不是等待0毫秒,0表示无限等待,直至线程被唤醒
//注意:是指当前运行的线程会等待,不是指调用了wait的线程会等待
wait(0);
}
} else {
//如果参数大于0
while (isAlive()) { //线程是否存活
long delay = millis - now; //计算需要等待多少秒
if (delay <= 0) {
break; //到时了,跳出循环
}
wait(delay); //等待delay毫秒
now = System.currentTimeMillis() - base; //现在过了几毫秒
}
}
}
简单的来说,join(参数)方法,其实就是让当前的线程等待(调用wait()会释放锁),然后就绪状态下的线程可以获得锁运行,运行结束后,之前等待的所有线程全部被唤醒,相当于调用了notifyAll()方法。下面看个图先:
看完图后接着讲关于join方法的例子
public class JoinThreadTest {
public static void main(String[] args) throws InterruptedException {
//线程1
Thread t = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("a:" + i);
}
}
};
//线程2
Thread t2 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("b:" + i);
}
}
};
//线程3
Thread t3 = new Thread() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("c:" + i);
}
}
};
//由上面图片可以知道,调用start()方法并不是立马执行,而是让线程进入就绪状态,等待锁的竞争
t.start();
t.join();
t3.start();
t2.start();
for (int i = 10; i < 20; i++)
System.out.println(123456);
}
}
在这里我主要讲解一些东东:
上面代码其实有四个线程,除了t,t2,t3还有main,main就是主线程。下面从 t.start();讲到尾
注意:在讲到 t.start();时,目前只有主线程在运行
- 首先执行了t.start(),则线程t进入就绪状态,等着和主线程竞争锁。
- 接着执行t.join(), 这里的t虽然调用了join( )方法,但是不是指让t去等待,上面源码说了,是让当前活着的线程等待,而目前活着的只有主线程(如果主线程不是活的他在怎么会执行t.join()),所以主线程会进入等待,直到t运行结束后,等待中的所有线程被唤醒,所以t执行完毕之后,会默认调用notifyAll(),唤醒主线程,让主线程从之前位置继续运行。
- 接着又执行t3.start(),则t3进入等待,和主线程竞争锁。
- 最后执行t2.start(),则t2也进入等待,和主线程和t3一起竞争抢锁。
- 上面代码讲至此,接着我说一下运行的结果(以上数据只是参考,数据太少可能看不效果,读者可以尝试让数据多一点就有效果了):
运行结果:首先t全部执行完毕,输出0-9,然后t3和t2和主线程交替输出(并发执行输出)
讲到这里你可能还不完全明白,我接着讲
如果上面部分代码变成下面的,那么是什么个情况?
t.start();
t3.start();
t3.join(); // join()换这里来了
for (int i = 10; i < 20; i++)
System.out.println(123456);
t2.start();
-
首先执行t.start();则t进入就绪状态,和主线程竞争锁,所以此过程中可能会出现这种情况
(one:t线程运行特别特别短然后结束了。two:t线程有点长,t运行了一下下,主函数接着运行,然后接着t运行…。three:t运气不好,还没得到锁) -
主函数接着运行t3.start();则t3进入就绪状态(在此过程中t可能在就绪状态【three】,也可能在运行【two】,也可能运行完了【one】)。
-
接着执行t3.join(),则此时对应上面的one、two、three分为几种可能情况:
【one】情况:t执行结束了即t线程死亡了,活着只有主线程,主线程执行t3.join() ,则主线程等待(因为当前轮到主线程运行),直至t3执行完毕,主线程唤醒
【two】情况:t可能运行到一半,接着执行了join(),此时存活的线程有t和main,但是执行t3.join()的是主线程,说明目前cpu是主线程在用,所以刚好主线程被wait()了,所以主线程等待,t和t3会交替执行。直至t3结束主线程才会醒(main唤醒和t3(调用join方法者)是否结束有关,和t无关),若此时t运行没结束,则和main一起交替执行。
【three】情况:执行join()时,t可能还在就绪状态,而main调用join(),则mian进入等待,而就绪的t和t3竞争锁,并发交替执行,直至t3结束,mian才会被唤醒。 -
最后执行for循环和t2.start(),for是属于主线的内容,会比t2优先执行
最后一个例子
t.join(); // join()换这里来了
t.start();
t3.start();
for (int i = 10; i < 20; i++)
System.out.println(123456);
t2.start();
- 这里的t.join有和没有效果都一样,为什么呢?我举个不太好的例子
- 比如,有4个跑步比赛的参与者,start()就表示他们已经进场了,准备跑步
- 而run()就表示他们已经在跑了
- 然后到场的每个人都有一个神奇的功能,就是join(),他可以让那个刚好在跑步的人停下来,等他跑完了,再让停下来的那个继续跑。
- 而上面代码先调用join再调用start,就相当于这个人还没到场就先有功能了,就和第4冲突了。
- 所以就是说,你这个线程都还没有start(),执行join()怎么可能会有效。
- 结论就是:只有先执行了start,join才会生效,反过来的话join不执行。
最最后一个例子
t.start();
t.join();
t3.start();
t3.join();
for (int i = 10; i < 20; i++)
System.out.println(123456);
t2.start();
t2.join();
到这里大家可能知道运行结果了,但我还是要讲一下子
- 首先主线程执行t.start(); t进入就绪状态
- 主线执行t.join(); 则主线程等待,后面代码主线程无法运行
- t线程结束,主线程醒了继续执行t3.start(); t3进入就绪
- 主线程执行t3.join(); 则主线程等待,后面代码主线程无法运行
- t3线程结束,主线程醒了继续执行for循环语句。
- 接着主线程执行t2.start(); t2进入就绪状态
- 主线程执行t2.join(); 则主线程等待,后面代码主线程无法运行
- 最终结果就是:输出t所以值,然后输入t3所以值,然后输出for所有值,然后输出t2所有值
总结一下子
通过上面例子可以知道,执行了join方法,其实就是让主线程等待,只要主线程一直活着while(isAlive()),就不让他醒来wait(0),除非把那个让主线程醒不来的线程想方法给干死(stop()【不建议】或者其他方式),不然主线程不会醒。