<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
問題:
sleep() 和 wait() 有什麼區別?
(1) sleep 是 Thread 方法,而 wait 是 Object 的方法 ;
(2) sleep 不需要強制和 synchronized 配合使用,但 wait 需要 和 synchronized 一起用 ;
(3) sleep 在睡眠的同時,不會釋放物件鎖的,但 wait 在等待的時候會釋放物件鎖 ;
(4) 它們 狀態 TIMED_WAITING;
@Slf4j public class Test1 { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (lock){ log.debug("t1執行緒獲得鎖...."); try { Thread.sleep(20000); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); // 等待1s,讓執行緒去執行t1執行緒 Thread.sleep(1000); // 獲取不到鎖,因為t1執行緒在睡眠期間並不會釋放鎖 synchronized (lock){ log.debug("主執行緒想要獲取鎖...."); } } }
執行sleep()方法後的結果:線上程t1睡眠期間,主執行緒沒有獲得鎖
10:34:34.574 [t1] DEBUG com.example.test.Test1 - t1執行緒獲得鎖....
@Slf4j public class Test1 { private static final Object lock = new Object(); public static void main(String[] args) throws InterruptedException { new Thread(()->{ synchronized (lock){ log.debug("t1執行緒獲得鎖...."); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } },"t1").start(); // 等待1s,讓執行緒去執行t1執行緒 Thread.sleep(1000); // 獲取不到鎖,因為t1執行緒在睡眠期間並不會釋放鎖 synchronized (lock){ log.debug("主執行緒想要獲取鎖...."); } } }
執行wait()方法後的結果:執行緒t1等待20s,線上程t1等待期間,主執行緒獲得了鎖
10:36:22.723 [t1] DEBUG com.example.test.Test1 - t1執行緒獲得鎖....
10:36:23.721 [main] DEBUG com.example.test.Test1 - 主執行緒想要獲取鎖....
場景:有幾個小孩都想進入房間內使用算盤(CPU)進行計算,老王(作業系統)就使用了一把鎖(synchronized)讓同一時間只有一個小孩能進入房間使用算盤,於是他們排隊進入房間。
(1) 小南最先獲取到了鎖,進入到房間內,但是由於條件不滿足(沒煙幹不了活),小南不能繼續進行計算 ,但小南如果一直佔用著鎖,其它人就得一直阻塞,效率太低。
(2) 於是老王單開了一間休息室(呼叫 wait 方法),讓小南到休息室(WaitSet)等著去了,這時鎖釋放開, 其它人可以由老王隨機安排進屋
(3) 直到小M將煙送來,大叫一聲 [ 你的煙到了 ] (呼叫 notify 方法)
(4) 小南於是可以離開休息室,重新進入競爭鎖的佇列
下面我們看如何正確的實現這個場景
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,預設沒有 private static boolean hasCigarette = 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("有煙,[{}],可以開始幹活了", hasCigarette); } } },"小南").start(); // 其他5個執行緒也想獲取鎖進入房間 for(int i=0;i<5;i++){ new Thread(()->{ synchronized (room){ log.debug("可以開始幹活了"); } },"其他人").start(); } // 主執行緒等待1s Thread.sleep(1000); // 因為小南執行緒使用sleep()方法,因此他在睡眠期間並不釋放鎖,送煙的沒辦法拿到鎖進入房間送煙 new Thread(()->{ synchronized (room){ hasCigarette = true; } },"送煙的").start(); } }
執行結果:
11:10:50.556 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:10:50.565 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:10:52.565 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:10:52.565 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
(1) 小南執行緒在睡眠期間並不釋放鎖,因此其他執行緒執行緒也沒辦法獲取到鎖進入房間,送煙執行緒就沒辦法送煙;
(2) 其它幹活的執行緒,都要一直阻塞,效率太低 ;
要解決上述的問題,需要使用wait-notify機制
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,預設沒有 private static boolean hasCigarette = 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("有煙,[{}],可以開始幹活了", hasCigarette); } } },"小南").start(); // 其他5個執行緒也想獲取鎖進入房間 for(int i=0;i<5;i++){ new Thread(()->{ synchronized (room){ log.debug("可以開始幹活了"); } },"其他人").start(); } // 主執行緒等待1s Thread.sleep(1000); // 送煙的,喚醒正在睡眠的小南執行緒 new Thread(()->{ synchronized (room){ hasCigarette = true; room.notify(); } },"送煙的").start(); } }
執行結果:
11:21:36.775 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:21:36.780 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:21:36.780 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:21:36.781 [其他人] DEBUG com.example.test.Test2 - 可以開始幹活了
11:21:37.773 [小南] DEBUG com.example.test.Test2 - 有煙沒?[true]
11:21:37.774 [小南] DEBUG com.example.test.Test2 - 有煙,[true],可以開始幹活了
解決了其他執行緒阻塞問題,但是如果有其他執行緒也在等待呢?就是說等待的執行緒不止小南一個,那麼會不會喚醒錯了呢?
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,預設沒有 private 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("有煙,[{}],可以開始幹活了", hasCigarette); }else { log.debug("沒幹成活.."); } } },"小南").start(); new Thread(()->{ synchronized (room){ log.debug("有外賣沒?[{}]", hasTakeout); if(!hasTakeout){ log.debug("沒外賣,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外賣沒?[{}]", hasTakeout); if(hasTakeout){ log.debug("有外賣,[{}],可以開始幹活了", hasTakeout); }else{ log.debug("沒幹成活.."); } } },"小女").start(); // 主執行緒等待1s Thread.sleep(1000); new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notify(); } },"送外賣的").start(); } }
執行結果:送外賣的應該叫醒小女但是卻把小南叫醒了
11:31:50.989 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:31:50.994 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:31:50.994 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:31:51.987 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:31:51.988 [小南] DEBUG com.example.test.Test2 - 沒幹成活..
notify 只能隨機喚醒一個 WaitSet 中的執行緒,這時如果有其它執行緒也在等待,那麼就可能喚醒不了正確的執行緒,稱之為【虛假喚醒】
解決方法:改為 notifyAll
new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notifyAll(); } },"送外賣的").start();
執行結果:
11:34:24.789 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:34:24.798 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:34:24.798 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:34:24.802 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:34:25.794 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[true]
11:34:25.794 [小女] DEBUG com.example.test.Test2 - 有外賣,[true],可以開始幹活了
11:34:25.794 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:34:25.795 [小南] DEBUG com.example.test.Test2 - 沒幹成活..
從結果可以看出小女幹成活,小南沒有幹成活。既然送煙的沒到,小南應該繼續等待才行,等送煙的來了再幹活。
用 notifyAll 僅解決某個執行緒的喚醒問題,但使用 if + wait 判斷僅有一次機會,一旦條件不成立,就沒有重新判斷的機會了
@Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,預設沒有 private 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); if(hasCigarette){ log.debug("有煙,[{}],可以開始幹活了", hasCigarette); }else { log.debug("沒幹成活.."); } } },"小南").start(); new Thread(()->{ synchronized (room){ log.debug("有外賣沒?[{}]", hasTakeout); if(!hasTakeout){ log.debug("沒外賣,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外賣沒?[{}]", hasTakeout); if(hasTakeout){ log.debug("有外賣,[{}],可以開始幹活了", hasTakeout); }else{ log.debug("沒幹成活.."); } } },"小女").start(); // 主執行緒等待1s Thread.sleep(1000); // 送煙的,喚醒正在睡眠的小南執行緒 new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notifyAll(); } },"送外賣的").start(); } } @Slf4j public class Test2 { private static final Object room = new Object(); // 是否有煙,預設沒有 private 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); if(hasCigarette){ log.debug("有煙,[{}],可以開始幹活了", hasCigarette); }else { log.debug("沒幹成活.."); } } },"小南").start(); new Thread(()->{ synchronized (room){ log.debug("有外賣沒?[{}]", hasTakeout); if(!hasTakeout){ log.debug("沒外賣,先歇會!"); try { room.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外賣沒?[{}]", hasTakeout); if(hasTakeout){ log.debug("有外賣,[{}],可以開始幹活了", hasTakeout); }else{ log.debug("沒幹成活.."); } } },"小女").start(); // 主執行緒等待1s Thread.sleep(1000); // 送煙的,喚醒正在睡眠的小南執行緒 new Thread(()->{ synchronized (room){ hasTakeout = true; log.debug("外賣到了...."); room.notifyAll(); } },"送外賣的").start(); } }
執行結果:當沒煙的時候,小南執行緒繼續等待,等待下一次判斷有煙的時候再幹活
11:38:36.206 [小南] DEBUG com.example.test.Test2 - 有煙沒?[false]
11:38:36.212 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[false]
11:38:36.212 [小女] DEBUG com.example.test.Test2 - 沒外賣,先歇會!
11:38:37.205 [送外賣的] DEBUG com.example.test.Test2 - 外賣到了....
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外賣沒?[true]
11:38:37.205 [小女] DEBUG com.example.test.Test2 - 有外賣,[true],可以開始幹活了
11:38:37.205 [小南] DEBUG com.example.test.Test2 - 沒煙,先歇會!
使用wait-notify的正確姿勢:
synchronized(lock) { while(條件不成立) { lock.wait(); } } //另一個執行緒 synchronized(lock) { lock.notifyAll(); }
呼叫wait()和notify()系列方法進行執行緒通訊的要點如下:
(1) 呼叫某個同步物件locko的wait()和notify()型別方法前,必須要取得這個鎖物件的監視鎖,所以wait()和notify()型別方法必須放在synchronized(locko)同步塊中,如果沒有獲得監視鎖,JVM就會報IllegalMonitorStateException異常。
(2) 呼叫wait()方法時使用while進行條件判斷,如果是在某種條件下進行等待,對條件的判斷就不能使用if語句做一次性判斷,而是使用while迴圈進行反覆判斷。只有這樣才能線上程被喚醒後繼續檢查wait的條件,並在條件沒有滿足的情況下繼續等待。
本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45