<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Java專欄:為什么wait和notify必須放在synchronized中?

          共 3849字,需瀏覽 8分鐘

           ·

          2022-03-01 02:55

          作者 | 磊哥

          來(lái)源 | Java面試真題解析(ID:aimianshi666)

          轉(zhuǎn)載請(qǐng)聯(lián)系授權(quán)(微信ID:GG_Stone)

          在多線程編程中,wait 方法是讓當(dāng)前線程進(jìn)入休眠狀態(tài),直到另一個(gè)線程調(diào)用了 notify 或 notifyAll 方法之后,才能繼續(xù)恢復(fù)執(zhí)行。而在 Java 中,wait 和 notify/notifyAll 有著一套自己的使用格式要求,也就是在使用 wait 和 notify(notifyAll 的使用和 notify 類似,所以下文就只用 notify 用來(lái)指代二者)必須配合 synchronized 一起使用才行。

          wait/notify基礎(chǔ)使用

          wait 和 notify 的基礎(chǔ)方法如下:

          Object?lock?=?new?Object();
          new?Thread(()?->?{
          ????synchronized?(lock)?{
          ????????try?{
          ????????????System.out.println("wait?之前");
          ????????????//?調(diào)用?wait?方法
          ????????????lock.wait();
          ????????????System.out.println("wait?之后");
          ????????}?catch?(InterruptedException?e)?{
          ????????????e.printStackTrace();
          ????????}
          ????}
          }).start();

          Thread.sleep(100);
          synchronized?(lock)?{
          ????System.out.println("執(zhí)行?notify");
          ????//?調(diào)用?notify?方法
          ????lock.notify();
          }

          以上代碼的執(zhí)行結(jié)果如下圖所示:

          wait/notify和synchronized一起用?

          那問題來(lái)了,是不是 wait 和 notify 一定要配合 synchronized 一起使用呢?wait 和 notify 單獨(dú)使用行不行呢?我們嘗試將以上代碼中的 synchronized 代碼行刪除,實(shí)現(xiàn)代碼如下:初看代碼好像沒啥問題,編譯器也沒報(bào)錯(cuò),好像能“正常使用”,然而當(dāng)我們運(yùn)行以上程序時(shí)就會(huì)發(fā)生如下錯(cuò)誤:從上述結(jié)果可以看出:無(wú)論是 wait 還是 notify,如果不配合 synchronized 一起使用,在程序運(yùn)行時(shí)就會(huì)報(bào) IllegalMonitorStateException 非法的監(jiān)視器狀態(tài)異常,而且 notify 也不能實(shí)現(xiàn)程序的喚醒功能了。

          原因分析

          從上述的報(bào)錯(cuò)信息我們可以看出,JVM 在運(yùn)行時(shí)會(huì)強(qiáng)制檢查 wait 和 notify 有沒有在 synchronized 代碼中,如果沒有的話就會(huì)報(bào)非法監(jiān)視器狀態(tài)異常(IllegalMonitorStateException),但這也僅僅是運(yùn)行時(shí)的程序表象,那為什么 Java 要這樣設(shè)計(jì)呢?其實(shí)這樣設(shè)計(jì)的原因就是為了防止多線程并發(fā)運(yùn)行時(shí),程序的執(zhí)行混亂問題。初看這句話,好像是用來(lái)描述“鎖”的。然而實(shí)際情況也是如此,wait 和 notify 引入鎖就是來(lái)規(guī)避并發(fā)執(zhí)行時(shí)程序的執(zhí)行混亂問題的。那這個(gè)“執(zhí)行混亂問題”到底是啥呢?接下來(lái)我們繼續(xù)往下看。

          wait和notify問題復(fù)現(xiàn)

          我們假設(shè) wait 和 notify 可以不加鎖,我們用它們來(lái)實(shí)現(xiàn)一個(gè)自定義阻塞隊(duì)列。這里的阻塞隊(duì)列是指讀操作阻塞,也就是當(dāng)讀取數(shù)據(jù)時(shí),如果有數(shù)據(jù)就返回?cái)?shù)據(jù),如果沒有數(shù)據(jù)則阻塞等待數(shù)據(jù),實(shí)現(xiàn)代碼如下:

          class?MyBlockingQueue?{
          ????//?用來(lái)保存數(shù)據(jù)的集合
          ????Queue?queue?=?new?LinkedList<>();

          ????/**
          ?????*?添加方法
          ?????*/

          ????public?void?put(String?data)?{
          ????????//?隊(duì)列加入數(shù)據(jù)
          ????????queue.add(data);?
          ????????//?喚醒線程繼續(xù)執(zhí)行(這里的線程指的是執(zhí)行?take?方法的線程)
          ????????notify();?//?③
          ????}

          ????/**
          ?????*?獲取方法(阻塞式執(zhí)行)
          ?????*?如果隊(duì)列里面有數(shù)據(jù)則返回?cái)?shù)據(jù),如果沒有數(shù)據(jù)就阻塞等待數(shù)據(jù)
          ?????*?@return
          ?????*/

          ????public?String?take()?throws?InterruptedException?{
          ????????//?使用?while?判斷是否有數(shù)據(jù)(這里使用?while?而非?if?是為了防止虛假喚醒)
          ????????while?(queue.isEmpty())?{?//?①??
          ????????????//?沒有任務(wù),先阻塞等待
          ????????????wait();?//?②
          ????????}
          ????????return?queue.remove();?//?返回?cái)?shù)據(jù)
          ????}
          }

          注意上述代碼,我們?cè)诖a中標(biāo)識(shí)了三個(gè)關(guān)鍵執(zhí)行步驟:①:判斷隊(duì)列中是否有數(shù)據(jù);②:執(zhí)行 wait 休眠操作;③:給隊(duì)列中添加數(shù)據(jù)并喚醒阻塞線程。如果不強(qiáng)制要求添加 synchronized,那么就會(huì)出現(xiàn)如下問題:

          步驟線程1線程2
          1執(zhí)行步驟 ① 判斷當(dāng)前隊(duì)列中沒有數(shù)據(jù)
          2
          執(zhí)行步驟 ③ 將數(shù)據(jù)添加到隊(duì)列,并喚醒線程1繼續(xù)執(zhí)行
          3執(zhí)行步驟 ② 線程 1 進(jìn)入休眠狀態(tài)

          從上述執(zhí)行流程看出問題了嗎?如果 wait 和 notify 不強(qiáng)制要求加鎖,那么在線程 1 執(zhí)行完判斷之后,尚未執(zhí)行休眠之前,此時(shí)另一個(gè)線程添加數(shù)據(jù)到隊(duì)列中。然而這時(shí)線程 1 已經(jīng)執(zhí)行過(guò)判斷了,所以就會(huì)直接進(jìn)入休眠狀態(tài),從而導(dǎo)致隊(duì)列中的那條數(shù)據(jù)永久性不能被讀取,這就是程序并發(fā)運(yùn)行時(shí)“執(zhí)行結(jié)果混亂”的問題。然而如果配合 synchronized 一起使用的話,代碼就會(huì)變成以下這樣:

          class?MyBlockingQueue?{
          ????//?用來(lái)保存任務(wù)的集合
          ????Queue?queue?=?new?LinkedList<>();

          ????/**
          ?????*?添加方法
          ?????*/

          ????public?void?put(String?data)?{
          ????????synchronized?(MyBlockingQueue.class)?{
          ????????????//?隊(duì)列加入數(shù)據(jù)
          ????????????queue.add(data);
          ????????????//?為了防止?take?方法阻塞休眠,這里需要調(diào)用喚醒方法?notify
          ????????????notify();?//?③
          ????????}
          ????}

          ????/**
          ?????*?獲取方法(阻塞式執(zhí)行)
          ?????*?如果隊(duì)列里面有數(shù)據(jù)則返回?cái)?shù)據(jù),如果沒有數(shù)據(jù)就阻塞等待數(shù)據(jù)
          ?????*?@return
          ?????*/

          ????public?String?take()?throws?InterruptedException?{
          ????????synchronized?(MyBlockingQueue.class)?{
          ????????????//?使用?while?判斷是否有數(shù)據(jù)(這里使用?while?而非?if?是為了防止虛假喚醒)
          ????????????while?(queue.isEmpty())?{??//?①
          ????????????????//?沒有任務(wù),先阻塞等待
          ????????????????wait();?//?②
          ????????????}
          ????????}
          ????????return?queue.remove();?//?返回?cái)?shù)據(jù)
          ????}
          }

          這樣改造之后,關(guān)鍵步驟 ① 和關(guān)鍵步驟 ② 就可以一起執(zhí)行了,從而當(dāng)線程執(zhí)行了步驟 ③ 之后,線程 1 就可以讀取到隊(duì)列中的那條數(shù)據(jù)了,它們的執(zhí)行流程如下:

          步驟線程1線程2
          1執(zhí)行步驟 ① 判斷當(dāng)前隊(duì)列沒有數(shù)據(jù)
          2執(zhí)行步驟 ② 線程進(jìn)入休眠狀態(tài)
          3
          執(zhí)行步驟 ③ 將數(shù)據(jù)添加到隊(duì)列,并執(zhí)行喚醒操作
          4線程被喚醒,繼續(xù)執(zhí)行
          5判斷隊(duì)列中有數(shù)據(jù),返回?cái)?shù)據(jù)

          這樣咱們的程序就可以正常執(zhí)行了,這就是為什么 Java 設(shè)計(jì)一定要讓 wait 和 notify 配合上 synchronized 一起使用的原因了。

          總結(jié)

          本文介紹了 wait 和 notify 的基礎(chǔ)使用,以及為什么 wait 和 notify/notifyAll 一定要配合 synchronized 使用的原因。如果 wait 和 notify/notifyAll 不強(qiáng)制和 synchronized 一起使用,那么在多線程執(zhí)行時(shí),就會(huì)出現(xiàn) wait 執(zhí)行了一半,然后又執(zhí)行了添加數(shù)據(jù)和 notify 的操作,從而導(dǎo)致線程一直休眠的缺陷。


          -- END?--

          -??| 更多精彩文章 -



          點(diǎn)擊關(guān)注下方公眾號(hào),每天學(xué) Java
          加我微信,交個(gè)朋友
          長(zhǎng)按/掃碼添加↑↑↑

          瀏覽 46
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  国产sm调教视频 | 性福利导航 | 欧美日韩大屌 | 国产搞鸡网站 | 欧美日韩视频伦理网页 |