成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Java多線程學習(四)等待/通知(wait/notify)機制

PiscesYE / 1690人閱讀

摘要:運行可運行狀態(tài)的線程獲得了時間片,執(zhí)行程序代碼。阻塞的情況分三種一等待阻塞運行的線程執(zhí)行方法,會把該線程放入等待隊列中。死亡線程方法執(zhí)行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。

系列文章傳送門:

Java多線程學習(一)Java多線程入門

Java多線程學習(二)synchronized關鍵字(1)

java多線程學習(二)synchronized關鍵字(2)

Java多線程學習(三)volatile關鍵字

Java多線程學習(四)等待/通知(wait/notify)機制

Java多線程學習(五)線程間通信知識點補充

Java多線程學習(六)Lock鎖的使用

Java多線程學習(七)并發(fā)編程中一些問題

系列文章將被優(yōu)先更新于微信公眾號“Java面試通關手冊”,歡迎廣大Java程序員和愛好技術的人員關注。

本節(jié)思維導圖:

思維導圖源文件+思維導圖軟件關注微信公眾號:“Java面試通關手冊” 回復關鍵字:“Java多線程” 免費領取。

一 等待/通知機制介紹 1.1 不使用等待/通知機制

當兩個線程之間存在生產和消費者關系,也就是說第一個線程(生產者)做相應的操作然后第二個線程(消費者)感知到了變化又進行相應的操作。比如像下面的whie語句一樣,假設這個value值就是第一個線程操作的結果,doSomething()是第二個線程要做的事,當滿足條件value=desire后才執(zhí)行doSomething()。

但是這里有個問題就是:第二個語句不停過通過輪詢機制來檢測判斷條件是否成立。如果輪詢時間的間隔太小會浪費CPU資源,輪詢時間的間隔太大,就可能取不到自己想要的數據。所以這里就需要我們今天講到的等待/通知(wait/notify)機制來解決這兩個矛盾

    while(value=desire){
        doSomething();
    }
1.2 什么是等待/通知機制?

通俗來講:

等待/通知機制在我們生活中比比皆是,一個形象的例子就是廚師和服務員之間就存在等待/通知機制。

廚師做完一道菜的時間是不確定的,所以菜到服務員手中的時間是不確定的;

服務員就需要去“等待(wait)”;

廚師把菜做完之后,按一下鈴,這里的按鈴就是“通知(nofity)”;

服務員聽到鈴聲之后就知道菜做好了,他可以去端菜了。

用專業(yè)術語講:

等待/通知機制,是指一個線程A調用了對象O的wait()方法進入等待狀態(tài),而另一個線程B調用了對象O的notify()/notifyAll()方法,線程A收到通知后退出等待隊列,進入可運行狀態(tài),進而執(zhí)行后續(xù)操作。上訴兩個線程通過對象O來完成交互,而對象上的wait()方法notify()/notifyAll()方法的關系就如同開關信號一樣,用來完成等待方和通知方之間的交互工作。

1.3 等待/通知機制的相關方法
方法名稱 描述
notify() 隨機喚醒等待隊列中等待同一共享資源的 “一個線程”,并使該線程退出等待隊列,進入可運行狀態(tài),也就是notify()方法僅通知“一個線程”
notifyAll() 使所有正在等待隊列中等待同一共享資源的 “全部線程” 退出等待隊列,進入可運行狀態(tài)。此時,優(yōu)先級最高的那個線程最先執(zhí)行,但也有可能是隨機執(zhí)行,這取決于JVM虛擬機的實現(xiàn)
wait() 使調用該方法的線程釋放共享資源鎖,然后從運行狀態(tài)退出,進入等待隊列,直到被再次喚醒
wait(long) 超時等待一段時間,這里的參數時間是毫秒,也就是等待長達n毫秒,如果沒有通知就超時返回
wait(long,int) 對于超時時間更細力度的控制,可以達到納秒
二 等待/通知機制的實現(xiàn) 2.1 我的第一個等待/通知機制程序

MyList.java

public class MyList {
    private static List list = new ArrayList();

    public static void add() {
        list.add("anyString");
    }

    public static int size() {
        return list.size();
    }

}

ThreadA.java

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                if (MyList.size() != 5) {
                    System.out.println("wait begin "
                            + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("wait end  "
                            + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

ThreadB.java

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock) {
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if (MyList.size() == 5) {
                        lock.notify();
                        System.out.println("已發(fā)出通知!");
                    }
                    System.out.println("添加了" + (i + 1) + "個元素!");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

Run.java

public class Run {

    public static void main(String[] args) {

        try {
            Object lock = new Object();

            ThreadA a = new ThreadA(lock);
            a.start();

            Thread.sleep(50);

            ThreadB b = new ThreadB(lock);
            b.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

運行結果:

從運行結果:"wait end 1521967322359"最后輸出可以看出,notify()執(zhí)行后并不會立即釋放鎖。下面我們會補充介紹這個知識點。

synchronized關鍵字可以將任何一個Object對象作為同步對象來看待,而Java為每個Object都實現(xiàn)了等待/通知(wait/notify)機制的相關方法,它們必須用在synchronized關鍵字同步的Object的臨界區(qū)內。通過調用wait()方法可以使處于臨界區(qū)內的線程進入等待狀態(tài),同時釋放被同步對象的鎖。而notify()方法可以喚醒一個因調用wait操作而處于阻塞狀態(tài)中的線程,使其進入就緒狀態(tài)。被重新喚醒的線程會視圖重新獲得臨界區(qū)的控制權也就是鎖,并繼續(xù)執(zhí)行wait方法之后的代碼。如果發(fā)出notify操作時沒有處于阻塞狀態(tài)中的線程,那么該命令會被忽略。

如果我們這里不通過等待/通知(wait/notify)機制實現(xiàn),而是使用如下的while循環(huán)實現(xiàn)的話,我們上面也講過會有很大的弊端。

 while(MyList.size() == 5){
        doSomething();
    }
2.2線程的基本狀態(tài)

上面幾章的學習中我們已經掌握了與線程有關的大部分API,這些API可以改變線程對象的狀態(tài)。如下圖所示:

新建(new):新創(chuàng)建了一個線程對象。

可運行(runnable):線程對象創(chuàng)建后,其他線程(比如main線程)調用了該對象的start()方法。該狀態(tài)的線程位于可運行線程池中,等待被線程調度選中,獲 取cpu的使用權。

運行(running):可運行狀態(tài)(runnable)的線程獲得了cpu時間片(timeslice),執(zhí)行程序代碼。

阻塞(block):阻塞狀態(tài)是指線程因為某種原因放棄了cpu使用權,也即讓出了cpu timeslice,暫時停止運行。直到線程進入可運行(runnable)狀態(tài),才有 機會再次獲得cpu timeslice轉到運行(running)狀態(tài)。阻塞的情況分三種:

(一). 等待阻塞:運行(running)的線程執(zhí)行o.wait()方法,JVM會把該線程放 入等待隊列(waitting queue)中。

(二). 同步阻塞:運行(running)的線程在獲取對象的同步鎖時,若該同步鎖 被別的線程占用,則JVM會把該線程放入鎖池(lock pool)中。

(三). 其他阻塞: 運行(running)的線程執(zhí)行Thread.sleep(long ms)或t.join()方法,或者發(fā)出了I/O請求時,JVM會把該線程置為阻塞狀態(tài)。當sleep()狀態(tài)超時join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入可運行(runnable)狀態(tài)。

死亡(dead):線程run()、main()方法執(zhí)行結束,或者因異常退出了run()方法,則該線程結束生命周期。死亡的線程不可再次復生。

備注:
可以用早起坐地鐵來比喻這個過程:

還沒起床:sleeping

起床收拾好了,隨時可以坐地鐵出發(fā):Runnable

等地鐵來:Waiting

地鐵來了,但要排隊上地鐵:I/O阻塞

上了地鐵,發(fā)現(xiàn)暫時沒座位:synchronized阻塞

地鐵上找到座位:Running

到達目的地:Dead

2.3 notify()鎖不釋放

當方法wait()被執(zhí)行后,鎖自動被釋放,但執(zhí)行玩notify()方法后,鎖不會自動釋放。必須執(zhí)行完otify()方法所在的synchronized代碼塊后才釋放。

下面我們通過代碼驗證一下:

(完整代碼:https://github.com/Snailclimb/threadDemo/tree/master/src/wait_notifyHoldLock)

帶wait方法的synchronized代碼塊

            synchronized (lock) {
                System.out.println("begin wait() ThreadName="
                        + Thread.currentThread().getName());
                lock.wait();
                System.out.println("  end wait() ThreadName="
                        + Thread.currentThread().getName());
            }

帶notify方法的synchronized代碼塊

            synchronized (lock) {
                System.out.println("begin notify() ThreadName="
                        + Thread.currentThread().getName() + " time="
                        + System.currentTimeMillis());
                lock.notify();
                Thread.sleep(5000);
                System.out.println("  end notify() ThreadName="
                        + Thread.currentThread().getName() + " time="
                        + System.currentTimeMillis());
            }

如果有三個同一個對象實例的線程a,b,c,a線程執(zhí)行帶wait方法的synchronized代碼塊然后bb線程執(zhí)行帶notify方法的synchronized代碼塊緊接著c執(zhí)行帶notify方法的synchronized代碼塊。

運行效果如下:

這也驗證了我們剛開始的結論:必須執(zhí)行完notify()方法所在的synchronized代碼塊后才釋放。

2.4 當interrupt方法遇到wait方法

當線程呈wait狀態(tài)時,對線程對象調用interrupt方法會出現(xiàn)InterrupedException異常。

Service.java

public class Service {
    public void testMethod(Object lock) {
        try {
            synchronized (lock) {
                System.out.println("begin wait()");
                lock.wait();
                System.out.println("  end wait()");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
            System.out.println("出現(xiàn)異常了,因為呈wait狀態(tài)的線程被interrupt了!");
        }
    }
}

ThreadA.java

public class ThreadA extends Thread {

    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }

}

Test.java

public class Test {

    public static void main(String[] args) {

        try {
            Object lock = new Object();

            ThreadA a = new ThreadA(lock);
            a.start();

            Thread.sleep(5000);

            a.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

}

運行結果:

參考:

《Java多線程編程核心技術》

《Java并發(fā)編程的藝術》

如果你覺得博主的文章不錯,歡迎轉發(fā)點贊。你能從中學到知識就是我最大的幸運。

歡迎關注我的微信公眾號:“Java面試通關手冊”(分享各種Java學習資源,面試題,以及企業(yè)級Java實戰(zhàn)項目回復關鍵字免費領取)。另外我創(chuàng)建了一個Java學習交流群(群號:174594747),歡迎大家加入一起學習,這里更有面試,學習視頻等資源的分享。

文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉載請注明本文地址:http://systransis.cn/yun/69100.html

相關文章

  • [Java并發(fā)-5]用“等待-通知機制優(yōu)化循環(huán)等待

    摘要:在這個等待通知機制中,我們需要考慮以下四個要素。何時等待線程要求的條件不滿足就等待。是會隨機地通知等待隊列中的一個線程,而會通知等待隊列中的所有線程。 由上一篇文章你應該已經知道,在 破壞占用且等待條件 的時候,如果轉出賬本和轉入賬本不滿足同時在文件架上這個條件,就用死循環(huán)的方式來循環(huán)等待,核心代碼如下: // 一次性申請轉出賬戶和轉入賬戶,直到成功 while(!actr.apply...

    fxp 評論0 收藏0
  • Java線程學習(六)Lock鎖的使用

    摘要:返回與此鎖相關聯(lián)的給定條件等待的線程數的估計。查詢是否有線程正在等待獲取此鎖。為公平鎖,為非公平鎖線程運行了獲得鎖定運行結果公平鎖的運行結果是有序的。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵字(2) Java多線程學習(三)volatile關鍵字 ...

    Caicloud 評論0 收藏0
  • Java線程匯總

    摘要:線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問題。用戶線程在前臺,守護線程在后臺運行,為其他前臺線程提供服務。當所有前臺線程都退出時,守護線程就會退出。線程阻塞等待獲取某個對象鎖的訪問權限。 1、多線程介紹 多線程優(yōu)點 資源利用率好 程序設計簡單 服務器響應更快 多線程缺點 設計更復雜 上下文切換的開銷 增加資源消耗線程需要內存維護本地的堆棧,同時需要操作系統(tǒng)資源管理線程。...

    Lsnsh 評論0 收藏0
  • JAVA線程間通信簡介

    摘要:線程通信的目標是使線程間能夠互相發(fā)送信號。但是,這個標志已經被第一個喚醒的線程清除了,所以其余醒來的線程將回到等待狀態(tài),直到下次信號到來。如果方法調用,而非,所有等待線程都會被喚醒并依次檢查信號值。 線程通信的目標是使線程間能夠互相發(fā)送信號。另一方面,線程通信使線程能夠等待其他線程的信號。 showImg(http://segmentfault.com/img/bVbPLD); 例...

    CHENGKANG 評論0 收藏0
  • 并發(fā)學習筆記 (5)

    摘要:執(zhí)行會重新將設置為,并且通知喚醒其中一個若有的話在方法中調用了函數而處于等待狀態(tài)的線程。除此之外,我們需要記錄同一個線程重復對一個鎖對象加鎖的次數。競爭失敗的線程處于就緒狀態(tài),長期競爭失敗的線程就會饑餓。 tutorials site Locks in java Locks (and other more advanced synchronization mechanisms...

    meteor199 評論0 收藏0

發(fā)表評論

0條評論

PiscesYE

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<