使用场景
要想实现多个线程之间的协作,如:线程执行的先后顺序、获取某个线程执行的结果等等,就需要使用到线程通信,例如:生产者-消费者模型、线程阻塞线程唤醒等场景。
JDK提供的线程协调API
细分为:suspend()/resume()(已过时)、wait()/notify()、park()/unpark().
生产者-消费者模型案例
suspend()/resume()
suspend()挂起目标线程,resume() 恢复线程执行,该方式已被弃用。
该方式已被弃用主要原因是:
1、suspend()挂起线程,在导致线程暂停的同时,并不会去释放任何锁资源。其他线程都无法访问被它占用的锁。直到对应的线程执行 resume() 方法后,被挂起的线程才能继续,从而其它被阻塞在这个锁的线程才可以继续执行。
2、如果 resume() 操作出现在 suspend() 之前执行,那么线程将一直处于挂起状态,同时一直占用锁,这就产生了死锁。而且,对于被挂起的线程,它的线程状态还是 Runnable。
使用案例:
public class Demo {
//商店
public static Object shop = null;
//正常的suspend/resume
public void suspendResume() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
// 如果没商品,则进入等待
while (shop == null) {
System.out.println("1、进入等待");
Thread.currentThread().suspend();
}
System.out.println("2、买到商品,回家");
});
consumerThread.start();
// 3秒之后,生产商品
Thread.sleep(3000L);
shop = new Object();
System.out.println("3、通知消费者");
consumerThread.resume();
}
public static void main(String[] args) throws Exception {
new Demo().suspendResume();
}
}
执行结果:
wait()/notify()
这些方法只能由同一对象锁的持有者线程调用,也就是写在同步代码块里面,否则会抛出illegalMonitorStateException异常。
wait()方法导致当前线程等待,加入该对象的等待集合中,并且放弃当前持有的对象锁。notify()方法唤醒一个(随机)正在等待这个对象锁的线程,notifyAll()唤醒所有正在等待这个对象锁的线程。
注意:虽然会wait()自动解锁,但是对顺序有要求,如果在notify()被调用之后,才开始调用wait()方法的调用,线程会永远处于waiting状态。
使用案例:
public void waitNotify() throws Exception {
Object obj =new Object();
// 启动线程
Thread wait= new Thread(() -> {
synchronized (obj) {
// 如果没商品,则进入等待
while (shop == null) {
try {
System.out.println("1、进入等待");
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到商品,回家");
});
wait.start();
// 3秒之后,生产一个商品
Thread.sleep(3000L);
shop = new Object();
synchronized (obj) {
obj.notify();
System.out.println("3、通知消费者");
}
}
执行结果:
park()与unpark()
线程调用park则等待“许可”,unpark 方法为制定线程提供“许可(permit)”
不要求park和unpark方法的调用顺序。多次调用unpark之后,再调用park,线程会直接运行。但不会叠加,也就是说,连续多次调用park方法,第一次会拿到“许可”直接运行,后续调用会进入等待。
public void parkUnpark() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
// 如果没商品,则进入等待
while (shop == null) {
System.out.println("1、进入等待");
LockSupport.park();
}
System.out.println("2、买到商品,回家");
});
consumerThread.start();
// 3秒之后,生产一个商品
Thread.sleep(3000L);
shop = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
调用结果:
JDK官方建议应该在循环中检查等待条件,不要用if,原因是处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足条件的情况下退出。伪唤醒是指线程并非因为notify\notifyall、unpark 等API调用而唤醒,是更底层的原因导致的。
//wait
synchronized(obj){
while(条件判断)
obj.wait();
...
}
//park
while(条件判断){
LockSupport.park();
...
}
总结
java线程协作的三种API对比如下:
API | 有同步锁是否释放锁 | 是否有顺序影响 |
---|---|---|
suspend()/resume() | 不释放 | 有 |
wait()/notify() | 释放 | 有 |
park()/unpark() | 不释放 | 无(但是至少一次调用unpark()) |