開始之前先看看,sleep(long n)
和 wait(long n)
1) sleep 是 Thread 的靜態方法,而 wait 是 Object 的方法
2) sleep 不需要強制和 synchronized 配合使用,但 wait 需要和 synchronized 一起用
3) sleep 在睡眠的同時,不會釋放物件鎖的,但 wait 在等待的時候會釋放物件鎖
4) 它們狀態 TIMED_WAITING
建議:鎖物件加final修飾,這樣鎖物件不可變。
改進 1
static final Object room = new Object();
static boolean hasCigarette = false; // 是否有煙
static boolean hasTakeout = false;
思考下面的解決方案好不好,為什麼?
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)? 不能,sleep不會釋放物件鎖
hasCigarette = true;
log.debug("煙到了噢!");
}, "送煙的").start();
輸出
20:49:49.883 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:49:49.887 [小南] c.TestCorrectPosture - 沒煙,先歇會!
20:49:50.882 [送煙的] c.TestCorrectPosture - 煙到了噢!
20:49:51.887 [小南] c.TestCorrectPosture - 有煙沒?[true]
20:49:51.887 [小南] c.TestCorrectPosture - 可以開始幹活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:49:51.887 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:49:51.888 [其它人] c.TestCorrectPosture - 可以開始幹活了
-
其它幹活的執行緒,都要一直阻塞,效率太低
-
小南執行緒必須睡足 2s 後才能醒來,就算煙提前送到,也無法立刻醒來
-
加了 synchronized (room) 後,就好比小南在裡面反鎖了門睡覺,煙根本沒法送進門,main 沒加 synchronized 就好像 main 執行緒是翻窗戶進來的
-
解決方法,使用 wait - notify 機制
改進 2
思考下面的實現行嗎,為什麼?
new Thread(() -> {
synchronized (room) {
log.debug("有煙沒?[{}]", hasCigarette);
if (!hasCigarette) {
log.debug("沒煙,先歇會!");
try {
room.wait(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();
}
sleep(1);
new Thread(() -> {
synchronized (room) {
hasCigarette = true;
log.debug("煙到了噢!");
room.notify();
}
}, "送煙的").start();
輸出
20:51:42.489 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:51:42.493 [小南] c.TestCorrectPosture - 沒煙,先歇會!
20:51:42.493 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:51:42.493 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:51:42.494 [其它人] c.TestCorrectPosture - 可以開始幹活了
20:51:43.490 [送煙的] c.TestCorrectPosture - 煙到了噢!
20:51:43.490 [小南] c.TestCorrectPosture - 有煙沒?[true]
20:51:43.490 [小南] c.TestCorrectPosture - 可以開始幹活了
-
解決了其它幹活的執行緒阻塞的問題
-
但如果有其它執行緒也在等待條件呢?
改進 3
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();
輸出
20:53:12.173 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:53:12.176 [小南] c.TestCorrectPosture - 沒煙,先歇會!
20:53:12.176 [小女] c.TestCorrectPosture - 外賣送到沒?[false]
20:53:12.176 [小女] c.TestCorrectPosture - 沒外賣,先歇會!
20:53:13.174 [送外賣的] c.TestCorrectPosture - 外賣到了噢!
20:53:13.174 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:53:13.174 [小南] c.TestCorrectPosture - 沒幹成活...
-
notify 只能隨機喚醒一個 WaitSet 中的執行緒,這時如果有其它執行緒也在等待,那麼就可能喚醒不了正確的執行緒,稱之為【虛假喚醒】
-
解決方法,改為 notifyAll
改進 4
new Thread(() -> {
synchronized (room) {
hasTakeout = true;
log.debug("外賣到了噢!");
room.notifyAll();
}
}, "送外賣的").start();
輸出
20:55:23.978 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:55:23.982 [小南] c.TestCorrectPosture - 沒煙,先歇會!
20:55:23.982 [小女] c.TestCorrectPosture - 外賣送到沒?[false]
20:55:23.982 [小女] c.TestCorrectPosture - 沒外賣,先歇會!
20:55:24.979 [送外賣的] c.TestCorrectPosture - 外賣到了噢!
20:55:24.979 [小女] c.TestCorrectPosture - 外賣送到沒?[true]
20:55:24.980 [小女] c.TestCorrectPosture - 可以開始幹活了
20:55:24.980 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:55:24.980 [小南] c.TestCorrectPosture - 沒幹成活...
-
用 notifyAll 僅解決某個執行緒的喚醒問題,但使用 if + wait 判斷僅有一次機會,一旦條件不成立,就沒有重新判斷的機會了
-
解決方法,用 while + wait,當條件不成立,再次 wait
改進 5
將 if 改為 while
if (!hasCigarette) {
log.debug("沒煙,先歇會!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
改動後
while (!hasCigarette) {
log.debug("沒煙,先歇會!");
try {
room.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
輸出
20:58:34.322 [小南] c.TestCorrectPosture - 有煙沒?[false]
20:58:34.326 [小南] c.TestCorrectPosture - 沒煙,先歇會!
20:58:34.326 [小女] c.TestCorrectPosture - 外賣送到沒?[false]
20:58:34.326 [小女] c.TestCorrectPosture - 沒外賣,先歇會!
20:58:35.323 [送外賣的] c.TestCorrectPosture - 外賣到了噢!
20:58:35.324 [小女] c.TestCorrectPosture - 外賣送到沒?[true]
20:58:35.324 [小女] c.TestCorrectPosture - 可以開始幹活了
20:58:35.324 [小南] c.TestCorrectPosture - 沒煙,先歇會!
while迴圈 + wait 防止虛假喚醒,並且一般使用notifyAll來進行喚醒
synchronized(lock) {
while(條件不成立) {
lock.wait();
}
// 幹活
}
//另一個執行緒
synchronized(lock) {
lock.notifyAll();
}