JDK提供的线程协调的API:
1、suspend、resume(已废弃)
对调用顺序有要求,也要开发自己注意锁的释放。这个被弃用的API, 容易死锁,也容易导致永久挂起。
2、wait、notify
wait/notify要求再同步关键字里面使用,但是一定要先调用wait,再调用notify,否则永久等待了
3、park、unpark
park/unpark没有顺序要求,但是park并不会释放锁,所有再同步代码中使用要注意
官方建议在循环中检查等待条件,因为处于等待状态的线程可能会因为错误或伪唤醒导致线程在没有满足结束条件的情况下退出。
一、suspend、resume很容易死锁,所以被弃用;
正常情况下:
Thread consumerThread = new Thread(() -> {
if (needWait == null) { //
System.out.println("1、消费者线程开始");
Thread.currentThread().suspend();
}
System.out.println("2、消费者线程结束");
});
consumerThread.start();
//
Thread.sleep(3000L);
needWait = new Object();
consumerThread.resume();
System.out.println("3、唤醒线程完成");
输出1、2、3
死锁情况1:在同步代码块中使用
Thread consumerThread = new Thread(() -> {
if (needWait == null) { //
System.out.println("1、消费者线程开始");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、消费者线程结束");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
needWait = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、唤醒线程完成");
输出1,并且程序不会执行完成;
原因:consumerThread线程获取锁并且挂起,导致主线程拿不到锁,程序卡死,不再继续向下执行,线程死锁了。
死锁情况2:
Thread consumerThread = new Thread(() -> {
if (needWait== null) {
System.out.println("1、消费者线程开始");
try { //
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、消费者线程结束");
});
consumerThread.start();
// 1秒之后,执行
Thread.sleep(1000L);
needWait= new Object();
consumerThread.resume();
System.out.println("3、唤醒线程完成");
consumerThread.join();
输出1、3,并且程序不会执行完成
原因:由于consumerThread因为种种原因,没能执行到挂起操作,而主线程先行执行了唤醒操作,再之后,consumerThread才执行的挂起操作,导致consumerThread一直在挂起,从而导致线程死锁。
二、wait、notify/notifyAll
该方式只能由同一对象锁的持有者线程调用,需要卸载同步代码块中,否则会抛出IllegalMonitorStateException。原因在于wait、notify采用监视器的方式进行管理。
wait方法将线程变为等待状态,并加入到该对象的等待集合中,放弃当前所持有的对象锁;
notify/notifyAll方法会唤醒该对象的等待集合中的一个/全部线程;
同suspend、resume相同,需要注意调用顺序的问题,否则将导致线程一直处于挂起状态。
正常使用情况:
Thread consumerThread = new Thread(() -> {
if (needWait == null) { //
synchronized (this) {
try {
System.out.println("1、消费者线程开始");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、消费者线程结束");
}).start();
//
Thread.sleep(3000L);
needWait= new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、唤醒线程完成");
}
输出1、2、3
死锁情况:
new Thread(() -> {
if (needWait== null) {
try {
Thread.sleep(3000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、消费者线程结束");
}).start();
//
Thread.sleep(1000L);
needWait = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、唤醒线程完成");
}
输出3、1,并且程序卡死,不再继续向下执行
原因:线程唤醒比线程挂起先执行,导致线程挂起后,没有操作进行唤醒。
三、park/unpark(java.util.concurrent.locks.LockSupport)
正常情况:
Thread consumerThread = new Thread(() -> {
if (needWait == null) {
System.out.println("1、进入等待");
LockSupport.park();
}
System.out.println("2、消费者线程结束");
});
consumerThread.start();
//
Thread.sleep(3000L);
needWait= new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、唤醒线程完成");
死锁情况:
Thread consumerThread = new Thread(() -> {
if (needWait == null) {
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、消费者线程结束");
});
consumerThread.start();
Thread.sleep(3000L);
needWait = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、唤醒线程完成");
输出1,程序卡死;
原因:同步代码块导致死锁;