线程协作
多线程协作的典型场景,生产者消费者模型。JAVA 中提供的线程阻塞和线程唤醒 Api
被弃用的 suspend 和 resume
作用:通过 suspend 挂起目标线程,通过 resume 唤醒线程。
代码演示:
public class Demo6 {
public static Object baozi;
// 正常的 suspend resume
public void suspendResumeTest() throws InterruptedException{
// 启动线程
Thread consumerThread = new Thread(() -> {
// 没有包子 则等待
if (baozi == null) {
System.out.println("1, 进入等待");
Thread.currentThread().suspend();
}
// 买到包子
System.out.println("2, 买到包子了");
});
// 启动进程
consumerThread.start();
// 主进程休眠3秒,让消费进程进入挂起
Thread.sleep(3000);
// 生产包子,通知消费进程
baozi = new Object();
consumerThread.resume();
System.out.println("3, 通知消费者");
}
public static void main(String[] args) throws Exception{
new Demo6().suspendResumeTest();
}
}
问题 :
- 不会释放锁
/** 死锁的suspend/resume。 suspend并不会像wait一样释放锁,故此容易写出死锁代码 */
public void suspendResumeDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println("3、通知消费者");
}
- 挂起必须在通知前
/** 导致程序永久挂起的suspend/resume */
public void suspendResumeDeadLockTest2() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) {
System.out.println("1、没包子,进入等待");
try { // 为这个线程加上一点延时
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
consumerThread.resume();
System.out.println("3、通知消费者");
consumerThread.join();
}
wait notify
wait 会让当前线程等待,加入该对象的等待集合中,并且释放当前持有的对象锁
notify/notityAll 用于h唤醒一个或所有等待对象锁的线程
/** 正常的wait/notify */
public void waitNotifyTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
问题 :
- 挂起必须在通知前
/** 会导致程序永久等待的wait/notify */
public void waitNotifyDeadLockTest() throws Exception {
// 启动线程
new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println("1、进入等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("2、买到包子,回家");
}).start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
synchronized (this) {
this.notifyAll();
System.out.println("3、通知消费者");
}
}
park unpark
park 等待许可,unpark 拿到许可,多次调用 unpark 之后,再调用park,线程会执行,但是 park 只有一次有效,不能叠加,也就是 拿到的令牌 只能唤醒一次,下次 park 继续等待。
/** 正常的park/unpark */
public void parkUnparkTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
LockSupport.park();
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
LockSupport.unpark(consumerThread);
System.out.println("3、通知消费者");
}
问题 :
- 不会释放锁
/** 死锁的park/unpark */
public void parkUnparkDeadLockTest() throws Exception {
// 启动线程
Thread consumerThread = new Thread(() -> {
if (baozidian == null) { // 如果没包子,则进入等待
System.out.println("1、进入等待");
// 当前线程拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("2、买到包子,回家");
});
consumerThread.start();
// 3秒之后,生产一个包子
Thread.sleep(3000L);
baozidian = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println("3、通知消费者");
}
伪唤醒
使用 if 判断进入等待是错误的,官方建议在循环中检查等待条件,如果不再循环中检查,程序就会在没有满足条件的情况下推出。
网友评论