下面的代码循序渐进,用来说明正确姿势。
使用变量进行开关传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.List;
class A { };
class B { };
@Slf4j(topic = "c.Run") public class Run {
static final Object room = new Object(); static boolean hasCigarette = false; static boolean hasTakeout = false;
public static void main(String[] args) throws Exception { 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(() -> { hasCigarette = true; log.debug("烟到了"); },"送烟的").start(); }
}
|
输出
1 2 3 4 5 6 7 8 9 10
| 00:28:48.506 [小南] DEBUG c.Run - 有烟没?[false] 00:28:48.511 [小南] DEBUG c.Run - 没烟,先歇会 00:28:49.507 [送烟的] DEBUG c.Run - 烟到了 00:28:50.511 [小南] DEBUG c.Run - 有烟没?[true] 00:28:50.511 [小南] DEBUG c.Run - 可以干活了 00:28:50.512 [其他人] DEBUG c.Run - 可以干活了 00:28:50.512 [其他人] DEBUG c.Run - 可以干活了 00:28:50.512 [其他人] DEBUG c.Run - 可以干活了 00:28:50.512 [其他人] DEBUG c.Run - 可以干活了 00:28:50.513 [其他人] DEBUG c.Run - 可以干活了
|
确实可以实现功能,但是,有以下的缺点
- 小南休息 2 秒的时候,其他人干不了活
- 小南必须睡足 2 秒才能醒来,即便是烟到了,也无法立刻醒来
wait && notify
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.List;
class A { };
class B { };
@Slf4j(topic = "c.Run") public class Run {
static final Object room = new Object(); static boolean hasCigarette = false; static boolean hasTakeout = false;
public static void main(String[] args) throws Exception { 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(); }
}
|
输出
1 2 3 4 5 6 7 8 9 10
| 00:31:14.501 [小南] DEBUG c.Run - 有烟没?[false] 00:31:14.506 [小南] DEBUG c.Run - 没烟,先歇会 00:31:14.506 [其他人] DEBUG c.Run - 可以干活了 00:31:14.506 [其他人] DEBUG c.Run - 可以干活了 00:31:14.506 [其他人] DEBUG c.Run - 可以干活了 00:31:14.507 [其他人] DEBUG c.Run - 可以干活了 00:31:14.507 [其他人] DEBUG c.Run - 可以干活了 00:31:15.505 [送烟的] DEBUG c.Run - 烟到了 00:31:15.505 [小南] DEBUG c.Run - 有烟没?[true] 00:31:15.505 [小南] DEBUG c.Run - 可以干活了
|
但是,这个有下面缺点
- 就是叫醒线程是随机的,如果,有其他线程会导致叫醒错误线程,即虚假唤醒
所以,可以把
1 2 3
| room.notify() 改成 room.notifyAll()
|
但是,依然会导致新的问题
虽然能全部唤醒进程,但是,有的进程可能没有满足条件,叫醒也没用。
把 if 换成 while 解决虚假唤醒
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
import java.util.ArrayList; import java.util.List;
class A { };
class B { };
@Slf4j(topic = "c.Run") public class Run {
static final Object room = new Object(); static boolean hasCigarette = false; static boolean hasTakeout = false;
public static void main(String[] args) throws Exception { 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("可以干活了"); } } }, "小南").start();
new Thread(() -> { synchronized (room) { log.debug("有外卖没?[{}]", hasTakeout); while (!hasTakeout) { log.debug("没外卖,先歇会"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外卖没?[{}]", hasTakeout); if (hasTakeout) { 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(); }
}
|
输出为
1 2 3 4 5 6 7 8 9 10 11 12
| 00:39:26.208 [小南] DEBUG c.Run - 有烟没?[false] 00:39:26.212 [小南] DEBUG c.Run - 没烟,先歇会 00:39:26.212 [其他人] DEBUG c.Run - 可以干活了 00:39:26.212 [其他人] DEBUG c.Run - 可以干活了 00:39:26.212 [其他人] DEBUG c.Run - 可以干活了 00:39:26.212 [其他人] DEBUG c.Run - 可以干活了 00:39:26.212 [其他人] DEBUG c.Run - 可以干活了 00:39:26.212 [小女] DEBUG c.Run - 有外卖没?[false] 00:39:26.212 [小女] DEBUG c.Run - 没外卖,先歇会 00:39:27.209 [送烟的] DEBUG c.Run - 烟到了 00:39:27.209 [小南] DEBUG c.Run - 有烟没?[true] 00:39:27.209 [小南] DEBUG c.Run - 可以干活了
|
总结
标准模版。
1 2 3 4 5 6 7 8 9 10 11
| synchronized(lock){ while(条件不成立){ lock.wait() } }
synchronized(lock){ lock.notifyAll() }
|
这里可能存在一个疑问点是
当条件不成立的时候,会一直在 while
的体内,不断的循环吗?
答案是,会。当条件不成立,会进入到 lock.wait()
,然后线程会进入到 waitset
,等再次被唤醒,如果条件不成立,还是会进入到循环。
可以运行下面的代码进行验证。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| package com.redisc;
import lombok.extern.slf4j.Slf4j;
class Downloader { public static String download() throws InterruptedException { Thread.sleep(5000); return "success"; } };
@Slf4j(topic = "c.Run") public class Run {
public static void main(String[] args) throws Exception { GuardedObject guardedObject = new GuardedObject(); new Thread(() -> { log.debug("等待结果"); Object state = (String) guardedObject.get(); log.debug("下载结果[{}]", state); }, "t1").start();
new Thread(() -> { log.debug("开始下载"); try { String state = Downloader.download(); log.debug("真实唤醒"); guardedObject.complete(state); } catch (InterruptedException e) { e.printStackTrace(); } }, "t2").start();
new Thread(() -> { log.debug("虚假唤醒"); try { Thread.sleep(1000); guardedObject.complete(null); } catch (Exception e) { e.printStackTrace(); } }, "t3").start(); }
}
class GuardedObject { private Object response;
public Object get() { synchronized (this) { while (response == null) { System.out.println("我被唤醒了"); try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return response; } }
public void complete(Object response) { synchronized (this) { this.response = response; this.notifyAll(); } } }
|
输出
1 2 3 4 5 6 7
| 12:09:48.655 [t2] DEBUG c.Run - 开始下载 12:09:48.655 [t1] DEBUG c.Run - 等待结果 我被唤醒了 12:09:48.655 [t3] DEBUG c.Run - 虚假唤醒 我被唤醒了 12:09:53.660 [t2] DEBUG c.Run - 真实唤醒 12:09:53.660 [t1] DEBUG c.Run - 下载结果[success]
|