等待唤醒机制总的来说共有三种
1.被弃用的suspend/resume
2.wait/notify机制
3.park/unpark机制
1.被弃用的suspend/resume
调用suspend挂起目标线程,通过resume可以恢复线程执行
被弃用的主要原因是因为: 容易写出死锁的代码
比如:
public void test_suspend() throws Exception {
//开启一个线程,代表小朋友
Thread consumerThread = new Thread(new Runnable() {
@Override
public void run() {
if (iceCream == null) {
System.out.println("没有冰激凌,等待...");
try {
Thread.sleep(6000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.currentThread().suspend(); //挂起
}
System.out.println("小朋友买到冰激凌");
}
});
consumerThread.start();
Thread.sleep(3000L); // 3秒之后
iceCream = new Object(); //店员做好了冰激凌
System.out.println("通知小朋友");
consumerThread.resume(); //通知小朋友
}
执行后,可以看到,线程死锁了
2.wait/notify机制
- wait方法可使当前线程进入等待状态, 加入到该对象的等待集合中,而且会随之将该线程持有的锁给释放掉
- notify/notifyAll方法,唤醒一个/所有的正在等待对象锁的线程
注意点:
- wait自动解锁对顺序有要求, 如果在notify被调用之后才开始wait方法的调用,线程就会永远的处于WAITNG状态了
- wait/notify方法必须在同步代码块中进行调用,否则会抛出异常IllegalMonitorStateException
正常使用方式:
/**
* 正常的wait/notify
* @throws Exception
*/
public void test1_normal() throws Exception {
//开启一个线程
new Thread(new Runnable() {
@Override
public void run() {
while (iceCream == null) { //若没有冰激凌
synchronized (WaitNotify.class) {
System.out.println("小朋友拿到锁。。。");
try {
System.out.println("没有冰激凌,等待...");
WaitNotify.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("小朋友买到冰激凌");
}
}).start();
Thread.sleep(3000L);
//做了一个冰激凌
iceCream = new Object();
synchronized (WaitNotify.class) {
System.out.println("拿到锁。。。");
WaitNotify.class.notifyAll();
System.out.println("通知小朋友");
}
}
死锁示例代码:
/**
* 死锁
* @throws Exception
*/
public void test2_DeadLock() throws Exception {
//开启一个线程,代表小朋友
new Thread(new Runnable() {
@Override
public void run() {
if (iceCream == null) {
try {
Thread.sleep(5000L);
System.out.println("没有冰激凌,等待...");
synchronized (WaitNotify.class){
WaitNotify.class.wait();
}
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
System.out.println("小朋友买到冰激凌");
}
}).start();
Thread.sleep(3000L);
// 店员做了一个冰激凌
iceCream = new Object();
//通知小朋友
synchronized (WaitNotify.class){
// notifyAll方法在wait方法之前执行,会造成永久等待
WaitNotify.class.notifyAll();
}
System.out.println("通知小朋友");
}
3.park/unpark机制
- park方法等待“许可”, unpark方法提供“许可”
- 调用unpark之后,再调用park,线程会直接运行
- 提前调用的unpark不叠加,连续多次调用unpark后,第一次调用park后会拿到“许可”直接运行,后续调用会进入等待。
和wait/notify不同的点在于,park/unpark机制类似"通行证",如果提前拿到了"通行证",后面才进行"限制"的话,也可以正常使用"通行证"
正常使用方式:
/**
* 正常的park/unpark
* @throws Exception
*/
public void test1_normal() throws Exception {
//开启一个线程,代表小朋友
Thread consumerThread = new Thread(new Runnable() {
@Override
public void run() {
while (iceCream == null) {
System.out.println("没有冰激凌,等待...");
LockSupport.park();
}
System.out.println("小朋友买到冰激凌");
}
});
consumerThread.start();
Thread.sleep(3000L);
iceCream = new Object();
// 通知小朋友
LockSupport.unpark(consumerThread);
System.out.println("通知小朋友");
}
伪唤醒
1.是指线程并非因为notify、notifyall、unpark等api调用而意外唤醒,更底层原因导致的情况
2.官方建议应该在循环中检查等待条件而不是用if