1. wait()函数
当一个线程调用共享变量的wait方法时,该线程会被阻塞,直到发生(1)其他线程调用了该共享变量的notify()或notifyAll()方法;(2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。
当调用wait方法的线程没有事先获取对象的锁(synchronized),此时调用wait方法会抛出IllegalMonitorStateException异常。如下图:
如果当前线程持有多把锁,当前调用的wait方法只会释放当前共享变量上的锁。
wait方法还存在虚假唤醒的问题,即没有被其他线程调用notify()、notifyAll()进行通知,当前线程就自行从挂起状态变为可运行状态。
2. wait(long timeout )函数
与wait()相比,不同之处在于如果挂起的线程没有在指定的timeout时间内(毫秒)被其他线程调用notify()或者notifyAll()方法唤醒,那么该函数会因超时而返回。
如果传入的timeout为0 , 则该函数与wait()效果一样;
如果传入的timeout为负数,则会抛出IllegalArgumentException异常。
3. wait(long timeout , int nanos)函数
参数说明:
timeout - 等待时间(以毫秒为单位)。
nanos - 额外等待时间(以纳秒为单位)。
部分代码片段如下:
4. notify()函数
一个线程调用notify()方法后,会唤醒一个在该共享变量上调用wait方法后被挂起的线程。具体唤醒哪个线程是随机的。被唤醒后的线程不会马上从wait()方法返回并继续执行,它仍然需要等到获取了监视器锁后才可以返回。
唤醒wait方法的线程释放了共享变量上的锁,并不意味着被唤醒的线程就会获取到锁对象,它仍需要和其他线程竞争锁,只有获取到锁后才能继续执行。
示例如下:
运行结果如下图:
让threadA和threadB调用共享资源resource的wait方法,threadC调用notify。从运行结果可知,threadA首先获取到resource上的锁,然后通过wait方法挂起当前线程并释放锁,随后threadB获取到resource的锁并通过wait方法也将自己挂起,此时threadA与threadB进入resource的阻塞集合当中,等待被唤醒。threadC调用notify方法后,从阻塞集合中随机唤醒一条被wait挂起的线程,从运行结果中发现是threadA被唤醒。
5. notifyAll()函数
notifyAll不同于notify的随即唤醒一个wait线程,notifyAll会唤醒所有在该共享变量上由于wait而被挂起的线程。
运行结果:
与notify代码示例唯一不同的是把notify方法变为notifyAll方法,由运行结果可以看出,threadC调用的notifyAll方法会唤醒所有阻塞集合当中被wait挂起的线程,唤醒的线程又会竞争共享资源的锁,由运行结果可以看出,threadB先获取到resource的锁,然后线程执行完毕,随后threadA又来获取resource上的锁。
6.join()方法
用于等待线程执行终止,join是无参且无返回值的方法,由Thread类直接提供。
运行结果:
从运行结果可以看出,主线程分别会在调用threadA.join()和threadB.join()后阻塞,等待threadA和threadB执行完毕,而等待的时间正是threadA和threadB执行的时间。
7. sleep()方法
sleep是Thread类中的静态方法,会放弃当前指定时间内的执行权,不参与CPU调度,但是该线程仍然持有监视器资源。指定睡眠时间达到后,该线程会处于就绪状态,继续参与CPU调度。
运行结果:
threadA首先获取到resource的锁,然后sleep 5秒,由运行结果可以看出,在threadA睡眠期间,并不会放弃对共享资源的监视,直到threadA结束睡眠后执行完毕释放锁,threadB才能持有共享资源的锁,不会出现threadA和threadB交替的情况。
8. yield()方法
yield方法也是Thread类中的静态方法,其目的是在告诉线程调度器,“你分给我的时间片,我用不完,剩下部分也不想用了”。这是主动放弃CPU的方法,正常情况下,当一个线程把分配给自己的时间片用完后,线程调度器才会进行下一轮线程调度,而当线程调用yield方法时,意味着放弃自己剩下的时间,主动告知线程调度器可以开始下一轮的线程调度了。当线程调用yield时,会处于就绪状态,等待下一次调度。
运行结果:
yield与sleep都会放弃CPU执行权,其区别在于sleep方法调用时,线程会被阻塞挂起指定的时间,在这期间调用sleep的线程不会再被线程调度器调度。而yield方法调用后,线程只是让出了自己的剩余时间,当前线程并未阻塞,而是处于就绪状态,线程调度器下一次调度时仍有可能调度到当前线程。