Object中的wait、notify、notifyAll,可以用于线程间的通信,通过这三个方法完成线程在指定锁(监视器)上的等待与唤醒,这三个方法是以锁(监视器)为中心的通信方法
除了它们之外,还有用于线程调度、控制的方法,他们是sleep、yield、join方法,他们可以用于线程的协作,他们是围绕着线程的调度而来的
对于sleep始终有一个超时时间的设置,所以,尽管他是在监视器内睡着了,但是并不会导致死锁,因为他终究是要醒来的。如下,子线程休眠500毫秒,主线程50毫秒打印一次状态,ps:被挂起线程(在该例子中是调用了sleep的线程)的调用结果为:TIMED_WAITING。
借助于sleep方法,可以模拟线程的顺序执行,比如下面示例,两个阶段,第二个阶段将在第一个阶段执行之后才会执行(哪个线程里使用了Thread.sleep(),则这个线程休眠,比如stepOne这个子线程执行了Thread.sleep(1000),则stepOne这个子线程需要休眠1000ms)
三个版本的join方法
方法的实现过程,与wait也是非常类似,下面两个版本的方法一个调用join(0),一个参数校验后,调用join(millis),所以根本还是单参数版本的join方法
目的:一个线程,循环5次,每次sleep 1s,然后是主线程中打印信息。
while (isAlive()) {
wait(0);
}
而这个wait(0)就相当于是this.wait(0),this就是我们自己创建的那个线程thread,看看方法的签名是不是有一个synchronized。
isAlive()也是this.isAlive(),也就是如果当前线程thread是alive(已经启动,但是未终止),那么将持续等待,等待的临界资源就是我们创建的这个线程对象本身
所以这两行代码的含义就是:该线程是否还存活?如果存活,调用join的那个线程将会在这个对象上进行等待(进入该线程对象的等待集),也就是说调用一个线程的join方法,就是这个线程在等待,这个线程对象就是我们的锁对象(不要疑惑,Object都可以作为锁,Thread实例对象怎么不可以?)
肯定大家很奇怪,既然是等待,wait又不会自己醒来,那不是出问题了吗?其实线程结束后,会调用this.notifyAll,所以主线程main会被唤醒
如果传递的参数不为0,将会走到下面的分支,或wait指定时长,与上面的逻辑一致,只不过是有指定超时时长而已
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
手动版本的等待结束
只是将join方法换成了同步代码块,锁对象为那个线程的实例对象thread,调用他的wait方法,从结果上看,效果一样,(不过此处没有持续监测isAlive(),所以一旦主线程醒来,即使线程没有结束,也会继续,不能百分百确保main肯定等待线程结束)
不过要注意:注释中有说明,自己不要使用Thread类的实例对象作为锁对象,如果是现在这种场景,使用join即可
为什么?从我们目前来看,join方法就是以这个对象为锁,如果你自己在使用,又是wait又是notify(notifyAll)的,万一出现什么隐匿的问题咋办?
所以join方法的原理就是:将指定的Thread实例对象作为锁对象,在其上进行同步,只要那个线程还活着,那么就会持续等待(或者有限时长),线程终止之后会调用自身this.notifyAll,以通知在其上等待的线程。简单说,只要他活着大家就都等着, 他死了会通知,所以效果就是在哪里调用了谁的join,哪里就要等待这个线程结束,才能继续
为什么要在start之后?
如上面所示,将join改造成同步代码块如下所示,如果这段同步代码在start方法之前,看下结果,没有等待指定线程结束,main主线程就结束了
因为如果还没有调用start方法,那么isAlive是false(已开始未结束),主线程根本就不会等待,所以继续执行,然后继续到下面的start,然后主线程结束,所以,为什么join方法一定要在start之前?就是因为这个isAlive方法的校验,你没有start,isAlive就是false,就不会同步等待,所以必须要先start,然后才能join
对于join方法,有两个关键:
换一个说法:
在回顾下之前状态一文中的切换图,又了解了这几个方法后,应该对状态切换有了更全面的认识
对于yield方法,比较容易理解,只是简单地对于CPU时间片的“礼让”,除非循环yield,否则一次yield,下次该线程仍旧可能会抢占到CPU时间片,可能方法调用和不调用没差别
sleep是静态方法,针对当前线程,进入休眠状态,两个版本的sleep方法始终有时间参数,所以必然会在指定的时间内苏醒,他也不会释放锁,当然,sleep方法的调用非必须在同步方法(同步代码块)内
join是实例方法,表示等待谁,是用于线程顺序的调度方法,可以做到一个线程等待另外一个线程,join有三个版本,指定超时时间或者持续等待直到目标线程执行结束,join也无需在同步方法(同步代码块)内
sleep和join都是可中断方法,被其他线程中断时,都会抛出InterruptedException异常,并且会醒来
join方法底层依赖wait,我们对比下wait与sleep
https://www.toutiao.com/i6797290803610780171