文章目录
一、 Wait/Notify
1. 原理和API
wait() 方法会释放对象的锁
,进入 WaitSet 等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify 为止
wait(long n) 有时限的等待
, 到 n 毫秒后结束等待继续向下执行,或是被 notify
(1) 进入Object监视器的线程才能调用wait()方法:
@Slf4j(topic = "c.Test18")
public class Test18 {
static final Object lock = new Object();
public static void main(String[] args) {
//只有成为了owner(获得了锁对象)才能调用wait()方法
synchronized (lock) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
(2) 使用notify()唤醒等待区的一个线程 或 notifyAll()唤醒等待区所有的线程:
@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
final static Object obj = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
//被唤醒之后继续执行这个代码
log.debug("其它代码....");
}
},"t1").start();
new Thread(() -> {
synchronized (obj) {
log.debug("执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
//被唤醒之后继续执行这个代码
log.debug("其它代码....");
}
},"t2").start();
// 主线程两秒后执行
sleep(2);
log.debug("唤醒 obj 上其它线程");
synchronized (obj) {
// obj.notify(); // 唤醒obj上一个线程
obj.notifyAll(); // 唤醒obj上所有等待线程
}
}
}
notifyAll()的执行结果:
20:41:39.046 c.TestWaitNotify [t2] - 执行....
20:41:39.080 c.TestWaitNotify [t1] - 执行....
20:41:41.037 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
20:41:41.037 c.TestWaitNotify [t1] - 其它代码....
20:41:41.037 c.TestWaitNotify [t2] - 其它代码....
notify()的执行结果:
20:42:39.865 c.TestWaitNotify [t1] - 执行....
20:42:39.874 c.TestWaitNotify [t2] - 执行....
20:42:41.863 c.TestWaitNotify [main] - 唤醒 obj 上其它线程
20:42:41.864 c.TestWaitNotify [t1] - 其它代码....
2. wait/notify的使用
1、wait(n)与sleep(n)的区别
(1) 开始之前先看看sleep(long n) 和 wait(long n) 的区别:
- sleep 是 Thread 方法,而 wait 是 Object 的方法
- sleep 不需要强制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
- sleep 在睡眠的同时,不会释放对象锁的,但 wait 在等待的时候会释放对象锁
- 线程状态相同: TIMED_WAITING
@Slf4j(topic = "c.Test19")
public class Test19 {
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
synchronized (lock) {
log.debug("获得锁");
try {
Thread.sleep(20000);
// lock.wait(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1").start();
Sleeper.sleep(1);
//主线程想要获取lock锁
synchronized (lock) {
log.debug("获得锁");
}
}
}
执行sleep()方法后的结果:在线程t1睡眠期间,主线程没有获得锁
20:57:34.663 c.Test19 [t1] - 获得锁
执行wait()方法后的结果:线程t1等待20s,在线程t1等待期间,主线程获得了锁
21:00:37.399 c.Test19 [t1] - 获得锁
21:00:38.397 c.Test19 [main] - 获得锁
2、使用过程
step1:
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep1 {
static final Object room = new Object();
static boolean hasCigarette = false; // 有没有烟
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
sleep(2);
}
log.debug("有烟没?[{}]", hasCigarette);
if (hasCigarette) {
log.debug("可以开始干活了");
}
}
}, "小南").start();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
synchronized (room) {
log.debug("可以开始干活了");
}
}, "其它人").start();
}
sleep(1);
new Thread(() -> {
// 这里能不能加 synchronized (room)?
// synchronized (room) {
hasCigarette = true;
// log.debug("烟到了噢!");
// }
}, "送烟的").start();
}
}
21:19:26.338 c.TestCorrectPosture [小南] - 有烟没?[false]
21:19:26.345 c.TestCorrectPosture [小南] - 没烟,先歇会!
21:19:28.345 c.TestCorrectPosture [小南] - 有烟没?[true]
21:19:28.345 c.TestCorrectPosture [小南] - 可以开始干活了
21:19:28.346 c.TestCorrectPosture [其它人] - 可以开始干活了
21:19:28.347 c.TestCorrectPosture [其它人] - 可以开始干活了
21:19:28.348 c.TestCorrectPosture [其它人] - 可以开始干活了
21:19:28.348 c.TestCorrectPosture [其它人] - 可以开始干活了
21:19:28.349 c.TestCorrectPosture [其它人] - 可以开始干活了
使用sleep()方法的缺陷:
step2:
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
room.wait();
//如果使用interrupt()方法打断等待中的线程会抛出线程,捕获异常后可以继续向下执行
} 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();
}
sleep(1);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("烟到了噢!");
//notify()方法也必须放在synchronized中,因为它也需要先拥有锁对象,才能执行
room.notify();
}
}, "送烟的").start();
}
}
21:27:50.830 c.TestCorrectPosture [其它人] - 可以开始干活了
21:27:50.835 c.TestCorrectPosture [其它人] - 可以开始干活了
21:27:50.836 c.TestCorrectPosture [小南] - 有烟没?[false]
21:27:50.841 c.TestCorrectPosture [小南] - 没烟,先歇会!
21:27:50.846 c.TestCorrectPosture [其它人] - 可以开始干活了
21:27:50.849 c.TestCorrectPosture [其它人] - 可以开始干活了
21:27:50.849 c.TestCorrectPosture [其它人] - 可以开始干活了
21:27:51.823 c.TestCorrectPosture [送烟的] - 烟到了噢!
21:27:51.824 c.TestCorrectPosture [小南] - 有烟没?[true]
21:27:51.824 c.TestCorrectPosture [小南] - 可以开始干活了
解决了其他线程阻塞问题,但是如果有其他线程也在等待呢?就是说等待的线程不止小南一个,那么会不会唤醒错了呢?
step3:
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep3 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
// 虚假唤醒
public static void main(String[] args) {
//小南线程等待烟
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();
sleep(1);
new Thread(() -> {
synchronized (room) {
//外卖到了
hasTakeout = true;
log.debug("外卖到了噢!");
//随机从等待区叫醒一个线程
room.notify();
}
}, "送外卖的").start();
}
}
送外卖的应该叫醒小南但是却把小女叫醒了:
21:36:20.549 c.TestCorrectPosture [小南] - 有烟没?[false]
21:36:20.555 c.TestCorrectPosture [小南] - 没烟,先歇会!
21:36:20.555 c.TestCorrectPosture [小女] - 外卖送到没?[false]
21:36:20.555 c.TestCorrectPosture [小女] - 没外卖,先歇会!
21:36:21.549 c.TestCorrectPosture [送外卖的] - 外卖到了噢!
21:36:21.550 c.TestCorrectPosture [小南] - 有烟没?[false]
21:36:21.550 c.TestCorrectPosture [小南] - 没干成活...
notify 只能随机唤醒一个 WaitSet 中的线程,这时如果有其它线程也在等待,那么就可能唤醒不了正确的线程,称之为【虚假唤醒】。
step4: 解决方法:改为 notifyAll
21:56:42.864 c.TestCorrectPosture [小南] - 有烟没?[false]
21:56:42.898 c.TestCorrectPosture [小南] - 没烟,先歇会!
21:56:42.899 c.TestCorrectPosture [小女] - 外卖送到没?[false]
21:56:42.900 c.TestCorrectPosture [小女] - 没外卖,先歇会!
21:56:43.850 c.TestCorrectPosture [送外卖的] - 外卖到了噢!
21:56:43.851 c.TestCorrectPosture [小女] - 外卖送到没?[true]
21:56:43.851 c.TestCorrectPosture [小女] - 可以开始干活了
21:56:43.852 c.TestCorrectPosture [小南] - 有烟没?[false]
21:56:43.852 c.TestCorrectPosture [小南] - 没干成活...
从结果可以看出小南干成活,小女干成活了:
用 notifyAll 仅解决某个线程的唤醒问题,但使用 if + wait 判断仅有一次机会,一旦条件不成立,就没有重新判断的机会了
step5: 解决方法,用 while + wait,当条件不成立,再次 wait,什么时候条件成立了才往下走,就是说只要送的不是烟而是外卖我就继续等待
@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep5 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
public static void main(String[] args) {
new Thread(() -> {
synchronized (room) {
log.debug("有烟没?[{}]", hasCigarette);
//条件不成立,就会进行下一轮的等待,而不会向下执行
while (!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);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("外卖送到没?[{}]", hasTakeout);
if (hasTakeout) {
log.debug("可以开始干活了");
} else {
log.debug("没干成活...");
}
}
}, "小女").start();
sleep(1);
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外卖到了噢!");
room.notifyAll();
}
}, "送外卖的").start();
}
}
22:05:09.560 c.TestCorrectPosture [小南] - 有烟没?[false]
22:05:09.578 c.TestCorrectPosture [小南] - 没烟,先歇会!
22:05:09.578 c.TestCorrectPosture [小女] - 外卖送到没?[false]
22:05:09.578 c.TestCorrectPosture [小女] - 没外卖,先歇会!
22:05:10.562 c.TestCorrectPosture [送外卖的] - 外卖到了噢!
22:05:10.563 c.TestCorrectPosture [小女] - 外卖送到没?[true]
22:05:10.563 c.TestCorrectPosture [小女] - 可以开始干活了
22:05:10.564 c.TestCorrectPosture [小南] - 没烟,先歇会!
使用wait / notify总结:
3. 保护性暂停模式
4. 生产者消费者模式
@Slf4j(topic = "c.Test21")
public class Test21 {
public static void main(String[] args) {
MessageQueue queue = new MessageQueue(2);
for (int i = 0; i < 3; i++) {
int id = i;
new Thread(() -> {
queue.put(new Message(id , "值"+id));
}, "生产者" + i).start();
}
new Thread(() -> {
while(true) {
sleep(1);
Message message = queue.take();
}
}, "消费者").start();
}
}
// 消息队列类 , java 线程之间通信
@Slf4j(topic = "c.MessageQueue")
class MessageQueue {
// 消息的队列集合,双向链表
private LinkedList<Message> list = new LinkedList<>();
// 队列容量
private int capcity;
public MessageQueue(int capcity) {
this.capcity = capcity;
}
// 获取消息
public Message take() {
// 检查队列是否为空
synchronized (list) {
//唤醒后判断队列不为空,就会向下执行取消息
while(list.isEmpty()) {
try {
log.debug("队列为空, 消费者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 从队列头部获取消息并返回
Message message = list.removeFirst();
log.debug("已消费消息 {}", message);
list.notifyAll();
return message;
}
}
// 存入消息
public void put(Message message) {
synchronized (list) {
// 检查对象是否已满
//唤醒后判断队列没满就会继续存消息
while(list.size() == capcity) {
try {
log.debug("队列已满, 生产者线程等待");
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 将消息加入队列尾部
list.addLast(message);
log.debug("已生产消息 {}", message);
//让等待中的线程都唤醒
list.notifyAll();
}
}
}
@Data
@Getter
@AllArgsConstructor
final class Message {
private int id;
private Object value;
}
结果:
22:51:18.539 c.MessageQueue [生产者0] - 已生产消息 Message(id=0, value=值0)
22:51:18.561 c.MessageQueue [生产者1] - 已生产消息 Message(id=1, value=值1)
22:51:18.562 c.MessageQueue [生产者2] - 队列已满, 生产者线程等待
22:51:19.549 c.MessageQueue [消费者] - 已消费消息 Message(id=0, value=值0)
22:51:19.549 c.MessageQueue [生产者2] - 已生产消息 Message(id=2, value=值2)
22:51:20.549 c.MessageQueue [消费者] - 已消费消息 Message(id=1, value=值1)
22:51:21.549 c.MessageQueue [消费者] - 已消费消息 Message(id=2, value=值2)
22:51:22.550 c.MessageQueue [消费者] - 队列为空, 消费者线程等待
二、park / unpark
public class TestParkUnpark {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("start...");
sleep(2);
log.debug("park...");
LockSupport.park();
log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);
}
}
结果:
22:56:48.547 c.TestParkUnpark [t1] - start...
22:56:49.537 c.TestParkUnpark [main] - unpark...
22:56:50.648 c.TestParkUnpark [t1] - park...
22:56:50.648 c.TestParkUnpark [t1] - resume...
与 Object 的 wait & notify 相比:
- park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll是唤醒所有等待线程,就不那么【精确】
- wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必
- park & unpark 可以先 unpark,而 wait & notify 不能先 notify
三、 线程状态装换
四、多把锁
看下面的一个线程代码:一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
public class TestMultiLock {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小南").start();
new Thread(() -> {
bigRoom.sleep();
},"小女").start();
}
}
@Slf4j(topic = "c.BigRoom")
class BigRoom {
public void sleep() {
synchronized (this) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (this) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}
当小南睡觉的时候占有着锁,小女就不能学习,解决的方法是使用多把锁:
public class TestMultiLock {
public static void main(String[] args) {
BigRoom bigRoom = new BigRoom();
new Thread(() -> {
bigRoom.study();
},"小南").start();
new Thread(() -> {
bigRoom.sleep();
},"小女").start();
}
}
@Slf4j(topic = "c.BigRoom")
class BigRoom {
private final Object studyRoom = new Object();
private final Object bedRoom = new Object();
public void sleep() {
synchronized (bedRoom) {
log.debug("sleeping 2 小时");
Sleeper.sleep(2);
}
}
public void study() {
synchronized (studyRoom) {
log.debug("study 1 小时");
Sleeper.sleep(1);
}
}
}
将锁的粒度细分:
好处,是可以增强并发度
坏处,如果一个线程需要同时获得多把锁,就容易发生死锁
五、线程活跃性
死锁:
有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁
t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁
t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁
@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
public static void main(String[] args) {
test1();
}
private static void test1() {
Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
synchronized (A) {
log.debug("lock A");
sleep(1);
synchronized (B) {
log.debug("lock B");
log.debug("操作...");
}
}
}, "t1");
Thread t2 = new Thread(() -> {
synchronized (B) {
log.debug("lock B");
sleep(0.5);
synchronized (A) {
log.debug("lock A");
log.debug("操作...");
}
}
}, "t2");
t1.start();
t2.start();
}
}
活锁:
活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如
@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
static volatile int count = 10;
static final Object lock = new Object();
public static void main(String[] args) {
new Thread(() -> {
// 期望减到 0 退出循环
while (count > 0) {
sleep(0.2);
count--;
log.debug("count: {}", count);
}
}, "t1").start();
new Thread(() -> {
// 期望超过 20 退出循环
while (count < 20) {
sleep(0.2);
count++;
log.debug("count: {}", count);
}
}, "t2").start();
}
}
六、ReentrantLock
synchronized与ReentrantLock的区别:
1. 可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
public class hh {
//创建锁对象
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
//给m1方法加了锁
lock.lock();
try {
log.debug("execute method1");
//m1还没解锁,又去调用m2
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
//给m2加锁
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
}
2. 可打断
可以避免无限制的等待锁,被动的避免死等:lock.lockInterruptibly()
public class hh {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
//可打断锁
//如果没有竞争,此方法就会获的Lock对象锁
//如果有竞争进入阻塞队列等待,可以被其他线程的interrupt方法打断,打断后不再等待直接进入catch语句块
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
//主线程先获了锁,t1线程就会进入阻塞队列进行等待
lock.lock();
log.debug("获得了锁");
t1.start();
try{
//主线程睡眠
sleep(1);
//t1线程等待时,可以打断,让其不再等待,直接向下指向catch语句块,不再获取锁
t1.interrupt();
} finally{
lock.unlock();
}
}
注意如果是不可中断模式,那么即使使用了 interrupt 也不会让等待中断:lock.lock()
public class hh {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("启动...");
try {
//注意如果是不可中断模式,那么即使使用了interrupt也不会让等待中断
lock.lock();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等锁的过程中被打断");
return;
}
try {
log.debug("获得了锁");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("获得了锁");
t1.start();
try{
//主线程睡眠
sleep(1);
t1.interrupt();
} finally{
lock.unlock();
}
}
3. 锁超时
lock.tryLock()
lock.tryLock(2, TimeUnit.SECONDS)
@Slf4j(topic = "c.Test22")
public class Test22 {
//创建锁对象
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
//尝试获得锁,成功为true,失败为false
//如果为false,不用向下执行
//等待2s
if (! lock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取不到锁");
return;
}
//如果为true
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1");
//主线程一直持有锁
lock.lock();
log.debug("获得到锁");
t1.start();
//主线程1s后释放了锁,t1线程等待2s,在等待时间内获得了锁
sleep(1);
log.debug("释放了锁");
lock.unlock();
}
}
ReentrantLock 默认是不公平的,主要是解决饥饿问题,但是没有必要设置公平锁,因为会降低并发度。
4. 条件变量
@Slf4j(topic = "c.Test24")
public class Test24 {
static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;
static ReentrantLock ROOM = new ReentrantLock();
// 等待烟的休息室
static Condition waitCigaretteSet = ROOM.newCondition();
// 等外卖的休息室
static Condition waitTakeoutSet = ROOM.newCondition();
public static void main(String[] args) {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没?[{}]", hasCigarette);
while (!hasCigarette) {
log.debug("没烟,先歇会!");
try {
waitCigaretteSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小南").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("外卖送到没?[{}]", hasTakeout);
while (!hasTakeout) {
log.debug("没外卖,先歇会!");
try {
waitTakeoutSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("可以开始干活了");
} finally {
ROOM.unlock();
}
}, "小女").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasTakeout = true;
waitTakeoutSet.signal();
} finally {
ROOM.unlock();
}
}, "送外卖的").start();
sleep(1);
new Thread(() -> {
ROOM.lock();
try {
hasCigarette = true;
waitCigaretteSet.signal();
} finally {
ROOM.unlock();
}
}, "送烟的").start();
}
}
10:16:47.228 c.Test24 [小女] - 外卖送到没?[false]
10:16:47.247 c.Test24 [小女] - 没外卖,先歇会!
10:16:47.248 c.Test24 [小南] - 有烟没?[false]
10:16:47.248 c.Test24 [小南] - 没烟,先歇会!
10:16:48.200 c.Test24 [小女] - 可以开始干活了
10:16:49.241 c.Test24 [小南] - 可以开始干活了