文章目录
- 前言
- 1. suspend / resume
- 正常执行
- 出现死锁
- 2. wait / notify
- 正常执行
- 出现死锁
- 3. park / unpark
- 正常执行
- 出现死锁
- 三种等待/通知机制的比较
前言
想要实作对个执行绪之间的协同(如:执行绪的执行顺序),就涉及到执行绪之间的通信,执行绪通信有许多种方式,比如,共享变量、讯息队列、档案共享等等,
下面,我们就一起来看看JDK api中提供的一种执行绪通信的方式——等待/通知机制,
jdk提供了三种实作执行绪通信的等待/通知机制:
- suspend / resume
- wait / notify
- park / unpark
生产者与消费者模式是一个典型的多执行绪并发协作的模式,在这个模式中,一部分执行绪被用于去生产资料,另一部分执行绪去处理资料,下面就是用等待/通知机制来实作生产者消费者模式,
注意:在下面的例子中,主执行绪充当生产者,new Thread()的执行绪充当消费者的角色,
1. suspend / resume
呼叫suspend挂起目标执行绪,通过resume可以恢复执行绪执行,
正常执行
/** 临界资源 */
public static Object resource = null;
public void suspendResumeTest() throws Exception {
// 启动执行绪
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果没有可用的临界资源,则进入等待
System.out.println(new Date() + " 进入等待............");
Thread.currentThread().suspend();
}
System.out.println("获取到临界资源,开始运行");
});
consumerThread.start();
// 3秒之后,生产一个资源
Thread.sleep(3000L);
resource = new Object();
System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
consumerThread.resume();
}
然而,suspend / resume这种等待/通知方式已经被弃用了,因为它非常容易写出死锁,表现在两个方面:
- suspend不会自动释放锁,从而导致死锁;
- 如果suspend 和 resume 的执行顺序搞反了就会导致程序永久挂起,从而形成死锁,
出现死锁
下面举例说明两种死锁的形成:
- suspend不会自动释放锁
/** 临界资源 */
public static Object resource = null;
public void suspendResumeDeadLockTest() throws Exception {
// 启动执行绪
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果没有可用的临界资源,则进入等待
System.out.println(new Date() + " 进入等待............");
// 当前执行绪拿到锁,然后挂起
synchronized (this) {
Thread.currentThread().suspend();
}
}
System.out.println("获取到临界资源,开始运行");
});
consumerThread.start();
// 3秒之后,生产一个资源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
consumerThread.resume();
}
System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
}
运行程序,发现程序被卡住了,执行绪一直被挂起,发生死锁,
- suspend 和 resume 的执行顺序不对
/** 临界资源 */
public static Object resource = null;
public void suspendResumeDeadLockTest2() throws Exception {
// 启动执行绪
Thread consumerThread = new Thread(() -> {
if (resource == null) {
System.out.println(new Date() + " 进入等待............");
try { // 为这个执行绪加上一点延时
Thread.sleep(5000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 这里的挂起执行在resume后面
Thread.currentThread().suspend();
}
System.out.println("获取到临界资源,开始运行");
});
consumerThread.start();
// 3秒之后,生产一个临界资源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
consumerThread.resume();
System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
consumerThread.join();
}
suspend 和 resume 的执行顺序搞反了就会导致程序永久挂起,从而形成死锁,
已经生产了临界资源被通知消费者执行绪,但程序一起挂起不再运行,
2. wait / notify
wait / notify 是 suspend / resume机制的一个替代机制,
wait方法导致当前执行绪等待,加入该物件的等待集合中,并且放弃当前持有的物件锁,也就是wait方法会自动释放锁,这样就减小了写出死锁的可能性,
wait / notify方法只能由同一物件锁的持有者执行绪呼叫,也就是写在同步块里面,否则会抛出lllegalMonitorStateException例外,
正常执行
/** 临界资源 */
public static Object resource = null;
public void waitNotifyTest() throws Exception {
// 启动执行绪
new Thread(() -> {
if (resource == null) { // 如果没有可用的临界资源,则进入等待
synchronized (this) {
try {
System.out.println(new Date() + " 进入等待............");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("获取到临界资源,开始运行");
}).start();
// 3秒之后,生产一个临界资源
Thread.sleep(3000L);
resource = new Object();
synchronized (this) {
this.notifyAll();
System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
}
}
出现死锁
虽然wait会自动解锁,但是对顺序有要求,如果在notify被呼叫之后,才开始wait方法的呼叫,执行绪会永远处于WAITING状态,
public void waitNotifyDeadLockTest() throws Exception {
// 启动执行绪
new Thread(() -> {
if (resource == null) { // 如果没有可用的临界资源,则进入等待
try {
Thread.sleep(5000L);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
synchronized (this) {
try {
System.out.println(new Date() + " 进入等待............");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("获取到临界资源,开始运行");
}).start();
// 3秒之后,生产一个临界资源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
synchronized (this) {
this.notifyAll();
System.out.println(" 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
}
}
3. park / unpark
执行绪呼叫park则等待“许可”,unpark方法为指定执行绪提供“许可(permit)”,
不要求park和unpark方法的呼叫顺序,
多次呼叫unpark之后,再呼叫park,执行绪会直接运行,但不会叠加,也就是说,连续多次呼叫park方法,第一次会拿到“许可”直接运行,后续呼叫会进入等待,
正常执行
public void parkUnparkTest() throws Exception {
// 启动执行绪
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果没有可用的临界资源,则进入等待
System.out.println(new Date() + " 进入等待............");
LockSupport.park();
}
System.out.println("获取到临界资源,开始运行");
});
consumerThread.start();
// 3秒之后,生产一个临界资源
Thread.sleep(3000L);
resource = new Object();
System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
LockSupport.unpark(consumerThread);
}
出现死锁
park并不会释放锁,所有再同步代码中使用可能会出现死锁
public void parkUnparkDeadLockTest() throws Exception {
// 启动执行绪
Thread consumerThread = new Thread(() -> {
if (resource == null) { // 如果没有可用的临界资源,则进入等待
System.out.println(new Date() + " 进入等待............");
// 当前执行绪拿到锁,然后挂起
synchronized (this) {
LockSupport.park();
}
}
System.out.println("获取到临界资源,开始运行");
});
consumerThread.start();
// 3秒之后,生产一个临界资源
Thread.sleep(3000L);
System.out.println(new Date());
resource = new Object();
// 争取到锁以后,再恢复consumerThread
synchronized (this) {
LockSupport.unpark(consumerThread);
}
System.out.println(new Date() + " 等待三秒生产了一个临界资源,通知消费者>>>>>>>>>>>>");
}
三种等待/通知机制的比较
suspend / resume | wait / notify | park/unpark |
---|---|---|
已弃用 | - | - |
suspend不释放锁 | wait释放锁 | park不释放锁 |
要求suspend / resume执行顺序 | 要求wait / notify执行顺序 | 不要求park/unpark执行顺序 |
0 评论