4.6 wait / notify
4.6.1 wait / notify 的原理
1. Thread-2 调用对象时,通过该对象访问其 Monitor 锁,查看 Owner 的值
2. 判断 Owner 是否为 null
若 Owner 不为 null,则进入 EntryList (阻塞状态 BLOCKED)
若 Owner 为 null,则将该线程存在 Owner 里 (拥有锁)
3. 判断 Owner 是否满足运行条件
若为是,则无需操作;
若为否,则调用 wait 方法进入 WaitSet(阻塞状态 WAITING)
线程进入 WaitSet 时会释放锁
BLOCKED 和 WAITING 两种阻塞状态的区别
BLOCKED 在 Owner 释放锁时被唤醒
WAITING 在调用 notify | notifyAll 方法时被唤醒
WaitSet 中的线程被唤醒后进入 EntryList
4.6.2 相关 API
wait() :让拥有锁的线程进入 WaitSet
notify() :唤醒 WaitSet 中的某一个线程
notifyAll() :唤醒 WaitSet 中的全部线程
上述三种 API 都属于 Object 对象中的方法,需先获得此对象的锁才可被调用
wait()、wait(0) :让拥有锁的线程进入 WaitSet 进行无限制的等待,直到被 notify | notifyAll 唤醒
wait(long timeout) :让拥有锁的进程进入 WaitSet 等待 timeout 毫秒后被唤醒,也可直接被 notify | notifyAll 唤醒
wait(long timeout) 和 sleep(long millis) 的相同点和不同点
1. 所属对象不同
wait(long timeout) 属于 Object 对象中的方法
sleep(long millis) 属于 Thread 对象中的方法
2. 调用约束不同
sleep(long millis) 方法的调用没有限制
Thread.sleep(long millis);
wait(long timeout) 方法的调用必须在 synchronized 代码块中
synchronized (对象) {
对象.wait(long timeout);
}
3. 锁操作不同
线程调用 sleep(long millis) 方法进入阻塞状态后不会释放锁
线程调用 wait(long timeout) 方法进入阻塞状态后会释放锁
4. 阻塞状态相同
线程调用 wait(long timeout) 和 sleep(long millis) 方法进入 TIMED_WAITING 状态
线程调用 wait() 方法进入 WAITING 状态
4.6.3 wait / notify 的使用
step 1
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Step1")
public class Step1 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(1000);
new Thread(() -> {
// 这里能不能加 synchronized (room)?
hasCigarette = true;
log.debug("烟到了噢!");
}, "送烟的").start();
}
}
// 运行结果
10:08:55 [小南] c.Step1 - 有烟没?[false]
10:08:55 [小南] c.Step1 - 没烟,先歇会!
10:08:56 [送烟的] c.Step1 - 烟到了噢!
10:08:57 [小南] c.Step1 - 有烟没?[true]
10:08:57 [小南] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
10:08:57 [其它人] c.Step1 - 可以开始干活了
进程已结束,退出代码 0
思考上面的解决方案好不好,为什么?
不好,因为线程调用 sleep(long millis) 方法进入阻塞状态后并不会释放锁,所以在该线程被阻塞的时间里,其他线程也在被阻塞,影响运行效率。
step 2
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Step2")
public class Step2 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("烟到了噢!");
room.notify();
}
}, "送烟的").start();
}
}
// 运行结果
10:25:52 [小南] c.Step2 - 有烟没?[false]
10:25:52 [小南] c.Step2 - 没烟,先歇会!
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:52 [其它人] c.Step2 - 可以开始干活了
10:25:53 [送烟的] c.Step2 - 烟到了噢!
10:25:53 [小南] c.Step2 - 有烟没?[true]
10:25:53 [小南] c.Step2 - 可以开始干活了
进程已结束,退出代码 0
思考上面的实现行吗,为什么?
不行,相较于 step 1 虽提高了运行效率,但又带来了新的问题【见 step 3】
step 3
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.step3")
public class step3 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notify();
}
}, "送外卖的").start();
}
}
// 运行结果
10:33:49 [小南] c.step3 - 有烟没?[false]
10:33:49 [小南] c.step3 - 没烟,先歇会!
10:33:49 [小女] c.step3 - 外卖送到没?[false]
10:33:49 [小女] c.step3 - 没外卖,先歇会!
10:33:50 [送外卖的] c.step3 - 外卖到了噢!
10:33:50 [小南] c.step3 - 有烟没?[false]
10:33:50 [小南] c.step3 - 没干成活...
如果 WaitSet 中有多个线程,那么该如何正确唤醒我们想要唤醒的线程呢?
1. 唤醒 WaitSet 中的全部线程(调用 notifyAll 方法)【见 step 4】
2. 由线程自行判断是否满足运行条件( while + wait )【见 step 5】
在 WaitSet 中有多个线程的条件下,若调用 notify 方法唤醒了不是我们想要唤醒的线程,这种现象称为 虚假唤醒
step 4
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.step3")
public class step3 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
if (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
// 运行结果
10:46:46 [小南] c.step3 - 有烟没?[false]
10:46:46 [小南] c.step3 - 没烟,先歇会!
10:46:46 [小女] c.step3 - 外卖送到没?[false]
10:46:46 [小女] c.step3 - 没外卖,先歇会!
10:46:47 [送外卖的] c.step3 - 外卖到了噢!
10:46:47 [小女] c.step3 - 外卖送到没?[true]
10:46:47 [小女] c.step3 - 可以开始干活了
10:46:47 [小南] c.step3 - 有烟没?[false]
10:46:47 [小南] c.step3 - 没干成活...
进程已结束,退出代码 0
step 5
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.step3")
public class step3 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("有烟没?[{}]", hasCigarette);
log.debug("可以开始干活了");
}
}, "小南").start();
new Thread(() -> {
synchronized (room) {
Thread thread = Thread.currentThread();
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
log.debug("可以开始干活了");
}
}, "小女").start();
Thread.sleep(1000);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
// 运行结果
10:50:14 [小南] c.step3 - 有烟没?[false]
10:50:14 [小南] c.step3 - 没烟,先歇会!
10:50:14 [小女] c.step3 - 外卖送到没?[false]
10:50:14 [小女] c.step3 - 没外卖,先歇会!
10:50:15 [送外卖的] c.step3 - 外卖到了噢!
10:50:15 [小女] c.step3 - 外卖送到没?[true]
10:50:15 [小女] c.step3 - 可以开始干活了
10:50:15 [小南] c.step3 - 没烟,先歇会!
wait/notify 的使用模版
synchronized (对象) {
while (!条件) {
对象.wait();
}
...
}
synchronized (对象) {
对象.notifyAll();
}
4.7 保护性暂停模式
保护性暂停模式(Guarded Suspension),适用于一个线程等待另一个线程的执行结果的情况,让这两个线程关联同一个 GuardedObject 即可。
4.7.1 GuardedObject
一个线程无限制地等待另一个线程的执行结果
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test8")
public class Test8 {
public static void main(String[] args) throws InterruptedException {
GuardedObject1 go1 = new GuardedObject1();
new Thread(() -> {
try {
Thread.sleep(1000);
log.debug("t1 线程开始运行");
go1.get("执行结果");
log.debug("t1 线程继续运行其他指令");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
log.debug("主线程开始运行");
log.debug("执行结果:{}",go1.waiting());
}
}
@Slf4j(topic = "c.GuardedObject1")
class GuardedObject1 {
private Object response;
public synchronized Object waiting() throws InterruptedException {
while (response == null) {
log.debug("等待 t1 线程的执行结果");
this.wait();
}
return response;
}
public synchronized void get(Object response) {
log.debug("t1 线程给出执行结果");
this.response = response;
log.debug("解除 WAITING 状态");
this.notifyAll();
}
}
// 运行结果
17:50:26 [main] c.Test8 - 主线程开始运行
17:50:26 [main] c.GuardedObject1 - 等待 t1 线程的执行结果
17:50:27 [t1] c.Test8 - t1 线程开始运行
17:50:27 [t1] c.GuardedObject1 - t1 线程给出执行结果
17:50:27 [t1] c.GuardedObject1 - 解除 WAITING 状态
17:50:27 [t1] c.Test8 - t1 线程继续运行其他指令
17:50:27 [main] c.Test8 - 执行结果:执行结果
进程已结束,退出代码 0
一个线程有限时地等待另一个线程的执行结果
情况一:等待时长 < 获取执行结果所花费的时间
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) throws InterruptedException {
GuardedObject2 go2 = new GuardedObject2();
new Thread(() -> {
try {
Thread.sleep(2000);
log.debug("t1 线程开始运行");
go2.get(null);
Thread.sleep(2000);
go2.get("执行结果");
log.debug("t1 线程继续运行其他指令");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
log.debug("主线程开始运行");
log.debug("执行结果:{}", go2.waiting(3000));
}
}
@Slf4j(topic = "c.GuardedObject2")
class GuardedObject2 {
private Object response;
public synchronized Object waiting(long timeout) throws InterruptedException {
// duration 线程等待时长
long duration = 0;
while (response == null) {
if (duration >= timeout) {
break;
}
timeout -= duration;
log.debug("等待 t1 线程的执行结果");
// begin 线程开始等待时间
long begin = System.currentTimeMillis();
this.wait(timeout);
// end 线程结束等待时间 (被唤醒或已等待 timeout 毫秒)
long end = System.currentTimeMillis();
duration = end - begin;
log.debug("等待时长:{}", duration);
}
return response;
}
public synchronized void get(Object response) {
log.debug("t1 线程给出执行结果");
this.response = response;
log.debug("解除 WAITING 状态");
this.notifyAll();
}
}
// 某次运行结果
10:13:27 [main] c.Test9 - 主线程开始运行
10:13:27 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:13:29 [t1] c.Test9 - t1 线程开始运行
10:13:29 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:13:29 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:13:29 [main] c.GuardedObject2 - 等待时长:1997
10:13:29 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:13:30 [main] c.GuardedObject2 - 等待时长:1012
10:13:30 [main] c.Test9 - 执行结果:null
10:13:31 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:13:31 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:13:31 [t1] c.Test9 - t1 线程继续运行其他指令
进程已结束,退出代码 0
情况二:等待时长 > 获取执行结果所花费的时间
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
@Slf4j(topic = "c.Test9")
public class Test9 {
public static void main(String[] args) throws InterruptedException {
GuardedObject2 go2 = new GuardedObject2();
new Thread(() -> {
try {
Thread.sleep(2000);
log.debug("t1 线程开始运行");
go2.get(null);
Thread.sleep(2000);
go2.get("执行结果");
log.debug("t1 线程继续运行其他指令");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "t1").start();
log.debug("主线程开始运行");
log.debug("执行结果:{}", go2.waiting(5000));
}
}
@Slf4j(topic = "c.GuardedObject2")
class GuardedObject2 {
private Object response;
public synchronized Object waiting(long timeout) throws InterruptedException {
// duration 线程等待时长
long duration = 0;
while (response == null) {
if (duration >= timeout) {
break;
}
timeout -= duration;
log.debug("等待 t1 线程的执行结果");
// begin 线程开始等待时间
long begin = System.currentTimeMillis();
this.wait(timeout);
// end 线程结束等待时间 (被唤醒或已等待 timeout 毫秒)
long end = System.currentTimeMillis();
duration = end - begin;
log.debug("等待时长:{}", duration);
}
return response;
}
public synchronized void get(Object response) {
log.debug("t1 线程给出执行结果");
this.response = response;
log.debug("解除 WAITING 状态");
this.notifyAll();
}
}
// 某次运行结果
10:17:46 [main] c.Test9 - 主线程开始运行
10:17:46 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:17:48 [t1] c.Test9 - t1 线程开始运行
10:17:48 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:17:48 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:17:48 [main] c.GuardedObject2 - 等待时长:1991
10:17:48 [main] c.GuardedObject2 - 等待 t1 线程的执行结果
10:17:50 [t1] c.GuardedObject2 - t1 线程给出执行结果
10:17:50 [t1] c.GuardedObject2 - 解除 WAITING 状态
10:17:50 [t1] c.Test9 - t1 线程继续运行其他指令
10:17:50 [main] c.GuardedObject2 - 等待时长:2004
10:17:50 [main] c.Test9 - 执行结果:执行结果
进程已结束,退出代码 0
duration 的值上下浮动为正常现象【问题不大】
N 个线程分别等待其他 N 个线程的返回结果
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
@Slf4j(topic = "c.Test10")
public class Test10 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
new People().start();
}
Thread.sleep(1000);
for (Integer id : Share.getIds()) {
new Postman(id, "内容" + id).start();
}
}
}
@Slf4j(topic = "c.People")
class People extends Thread {
@Override
public void run() {
try {
GuardedObject3 go3 = Share.createGuardedObject3();
Object response = go3.waiting(5000);
log.debug("开始收信");
log.debug("收信方id:{},信件内容:{}", go3.getId(), response);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Slf4j(topic = "c.Postman")
class Postman extends Thread {
private int id;
private Object response;
public Postman(int id, Object response) {
this.id = id;
this.response = response;
}
@Override
public void run() {
GuardedObject3 go3 = Share.getGuardedObject3(id);
log.debug("收信方id:{},信件内容:{}", id, response);
log.debug("开始送信");
go3.get(response);
}
}
class Share {
private static Map<Integer, GuardedObject3> map = new Hashtable<>();
private static int id = 1;
private static synchronized int obtainId() {
return id++;
}
public static GuardedObject3 getGuardedObject3(int id) {
return map.remove(id);
}
public static GuardedObject3 createGuardedObject3() {
GuardedObject3 go3 = new GuardedObject3(obtainId());
map.put(go3.getId(), go3);
return go3;
}
public static Set<Integer> getIds() {
return map.keySet();
}
}
class GuardedObject3 {
private int id;
public GuardedObject3(int id) {
this.id = id;
}
public int getId() {
return id;
}
private Object response;
public synchronized Object waiting(long timeout) throws InterruptedException {
// duration 线程等待时长
long duration = 0;
while (response == null) {
if (duration > timeout) {
break;
}
timeout -= duration;
// begin 线程开始等待时间
long begin = System.currentTimeMillis();
this.wait(timeout);
// end 线程结束等待时间 (被唤醒或已等待 timeout 毫秒)
long end = System.currentTimeMillis();
duration = end - begin;
}
return response;
}
public synchronized void get(Object response) {
this.response = response;
this.notifyAll();
}
}
// 某次运行结果
11:34:35 [Thread-3] c.Postman - 收信方id:3,信件内容:内容3
11:34:35 [Thread-5] c.Postman - 收信方id:1,信件内容:内容1
11:34:35 [Thread-4] c.Postman - 收信方id:2,信件内容:内容2
11:34:35 [Thread-5] c.Postman - 开始送信
11:34:35 [Thread-3] c.Postman - 开始送信
11:34:35 [Thread-4] c.Postman - 开始送信
11:34:35 [Thread-1] c.People - 开始收信
11:34:35 [Thread-0] c.People - 开始收信
11:34:35 [Thread-2] c.People - 开始收信
11:34:35 [Thread-1] c.People - 收信方id:2,信件内容:内容2
11:34:35 [Thread-0] c.People - 收信方id:1,信件内容:内容1
11:34:35 [Thread-2] c.People - 收信方id:3,信件内容:内容3
进程已结束,退出代码 0
4.8 生产者/消费者模式
保护性暂停模式中线程间为一对一关系,生产者/消费者模式中线程间为一对多关系(一个消费者对应多个生产者 )
消息队列遵从先进先出原则
消息队列有容量限制。队列为空时,消费者线程等待;队列已满时,生产者线程等待。
package com.rui.blocked;
import lombok.extern.slf4j.Slf4j;
import java.util.LinkedList;
public class Test11 {
public static void main(String[] args) {
MessageQueues queues = new MessageQueues(2);
for (int i = 0; i < 3; i++) {
int id = i;
new Thread(() -> {
try {
queues.put(new Message(id, "消费者" + id + "信息"));
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "生产者" + id).start();
}
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
queues.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "消费者").start();
}
}
@Slf4j(topic = "c.MessageQueues")
class MessageQueues {
// 消息队列
private LinkedList<Message> list = new LinkedList();
// 消息队列容量
private int capacity;
public MessageQueues(int capacity) {
this.capacity = capacity;
}
public Message take() throws InterruptedException {
synchronized (list) {
// 消息队列为空时,消费者线程等待
while (list.isEmpty()) {
log.debug("消息队列为空,消费者线程等待");
list.wait();
}
Message message = list.removeFirst();
log.debug("消费者线程 take " + message.getId());
list.notifyAll();
return message;
}
}
public void put(Message message) throws InterruptedException {
synchronized (list) {
// 消息队列已满时,生产者线程等待
while (list.size() == capacity) {
log.debug("消息队列已满,生产者线程等待");
list.wait();
}
list.addLast(message);
log.debug("生产者线程 put " + message.getId());
list.notifyAll();
}
}
}
class Message {
private int id;
private Object value;
public Message(int id, Object value) {
this.id = id;
this.value = value;
}
public int getId() {
return id;
}
public Object getValue() {
return value;
}
@Override
public String toString() {
return "Message{" +
"id=" + id +
", value=" + value +
'}';
}
}
// 某次运行结果
08:50:08 [生产者0] c.MessageQueues - 生产者线程 put 0
08:50:08 [生产者2] c.MessageQueues - 生产者线程 put 2
08:50:08 [生产者1] c.MessageQueues - 消息队列已满,生产者线程等待
08:50:09 [消费者] c.MessageQueues - 消费者线程 take 0
08:50:09 [生产者1] c.MessageQueues - 生产者线程 put 1
08:50:10 [消费者] c.MessageQueues - 消费者线程 take 2
08:50:11 [消费者] c.MessageQueues - 消费者线程 take 1
08:50:12 [消费者] c.MessageQueues - 消息队列为空,消费者线程等待
4.9 park / unpark
原理
park / unpark 是 LockSupport 类中的方法
// 暂停当前线程的运行
LockSupport.park();
// 恢复当前线程的运行
LockSupport.unpark();
package java.util.concurrent.locks;
import sun.misc.Unsafe;
public class LockSupport {
private static final sun.misc.Unsafe UNSAFE;
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
public static void park() {
UNSAFE.park(false, 0L);
}
}
package sun.misc;
public final class Unsafe {
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
}
图解
wait / notify / notifyAll 的调用顺序为 wait 在前,notify / notifyAll 在后
park / unpark 的调用顺序虽无先后之分,但会出现不同的情况
park 在前,unpark 在后
第一步,当前线程调用 LockSupport.park() 方法
第二步,查看 _counter 的值,此时该值为 0,获得 _mutex 互斥锁
第三步,线程进入 _cond 阻塞
第四步,将 _counter 赋值为 0
第五步,调用 LockSupport.unpark(Thread thread) 方法,将 _counter 赋值为 1
第六步,唤醒 _cond 中的 thread 线程
第七步,thread 恢复运行
第八步,将 _counter 赋值为 0
unpark 在前,park 在后
第一步,调用 LockSupport.unpark(Thread thread) 方法,将 _counter 赋值为 1
第二步,thread 线程调用 LockSupport.park() 方法
第三步,查看 _counter 的值,此时该值为 1,thread 线程运行不受影响
第四步,将 _counter 赋值为 0
文字
每个线程都有一个专属 Parker 对象,该对象由 _counter,_cond 和 _mutex 三部分组成
若将线程看做是一位自驾游旅行者;将 Parker 对象看做是该旅行者驾驶的车辆;将 _cond 看做是该车辆行驶的状态;将 _counter 看做是该车辆的油量。
那么,线程调用 park 方法可以看做是车辆驶入了加油站。若车辆的油量为 0,则表示该车辆需要停车加油;若车辆的油量为 1,则表示车辆可继续行驶,无需停车加油。
park 方法可以看做是在通过查看车辆的油量判断车辆行驶的状态;unpark 方法可以看作是在给车辆加油使其满足行驶条件。
park 在前,unpark 在后
第一步,查看车辆的油量,此时该油量为 0
第二步,车辆油量不足,无法行驶,停车等待加油
第三步,加油
第四步,车辆继续行驶
unpark 在前,park 在后
第一步,加油
第二步,查看车辆的油量,此时该油量为 1
第三步,车辆继续行驶
不论是上述哪种情况,最终 _counter 都会被赋值为 0,不太恰当的理解是:车辆继续行驶的油耗
说些废话
本篇文章为博主日常学习记录,故而会小概率存在各种错误,若您在浏览过程中发现一些,请在评论区指正,望我们共同进步,谢谢!