Java多線程中wait?notify等待喚醒機制詳解
一.等待喚醒機制簡介
由于線程之間是搶占式執(zhí)行的,因此線程的執(zhí)行順序難以預(yù)知。但是實際開發(fā)中有時候我們希望合理的協(xié)調(diào)多個線程之間的執(zhí)行先后順序。為了完成協(xié)調(diào)的工作,這里主要設(shè)計三個方法:
- wait() / wait(long timeout) : 讓當(dāng)前線程進入等待狀態(tài)
- notify() / notifyAll(): 喚醒在當(dāng)前對象上等待的線程
注意:wait,notify,notifyAll都是Object類的方法
二.synchronized與wait()與notify()
- synchronized的含義:
Java中每一個對象都可以成為一個監(jiān)視器(Monitor), 該Monitor由一個鎖(lock), 一個等待隊列(waiting queue ), 一個入口隊列( entry queue).
對于一個對象的方法, 如果沒有synchronized關(guān)鍵字, 該方法可以被任意數(shù)量的線程,在任意時刻調(diào)用。
對于添加了synchronized關(guān)鍵字的方法,任意時刻只能被唯一的一個獲得了對象實例鎖的線程調(diào)用。
synchronized用于實現(xiàn)多線程的同步操作
- wait()功能:
wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于線程同步
wait()總是在一個循;環(huán)中被調(diào)用,掛起當(dāng)前線程來等待一個條件的成立。 Wait調(diào)用會一直等到其他線程調(diào)用notifyAll()時才返回。
當(dāng)一個線程在執(zhí)行synchronized 的方法內(nèi)部,調(diào)用了wait()后, 該線程會釋放該對象的鎖, 然后該線程會被添加到該對象的等待隊列中(waiting queue), 只要該線程在等待隊列中, 就會一直處于閑置狀態(tài), 不會被調(diào)度執(zhí)行。 要注意wait()方法會強迫線程先進行釋放鎖操作,所以在調(diào)用wait()時, 該線程必須已經(jīng)獲得鎖,否則會拋出異常。由于wait()在synchonized的方法內(nèi)部被執(zhí)行, 鎖一定已經(jīng)獲得, 就不會拋出異常了。
- notify()的功能:
wait(), notify(), notifyAll() 和 synchonized 需要搭配使用, 用于線程同步
當(dāng)一個線程調(diào)用一個對象的notify()方法時, 調(diào)度器會從所有處于該對象等待隊列(waiting queue)的線程中取出任意一個線程, 將其添加到入口隊列( entry queue) 中. 然后在入口隊列中的多個線程就會競爭對象的鎖, 得到鎖的線程就可以繼續(xù)執(zhí)行。 如果等待隊列中(waiting queue)沒有線程, notify()方法不會產(chǎn)生任何作用
notifyAll() 和notify()工作機制一樣, 區(qū)別在于notifyAll()會將等待隊列(waiting queue)中所有的線程都添加到入口隊列中(entry queue)
三.等待喚醒機制案例
1.讓t1執(zhí)行wait()方法。
2.此時t2得到鎖,再讓t2執(zhí)行notify()方法釋放鎖。
3.此時t1得到鎖,t1會自動從wait()方法之后的代碼,繼續(xù)執(zhí)行。
4.通過上述流程,我們就可以清楚的看到,wait()和notify()各自是怎么工作的了,也可以知道兩者是怎么配合的了。
import java.util.*; public class Main { //創(chuàng)建一個將被兩個線程同時訪問的共享對象 public static Object loker = new Object(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(()->{ synchronized (loker){ System.out.println("線程一初次獲得對象鎖,執(zhí)行過程中調(diào)用鎖對象的wait()方法~~"); try { loker.wait(); System.out.println("當(dāng)線程一被喚醒后,后面的代碼繼續(xù)執(zhí)行"); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("線程一運行結(jié)束"); },"線程一"); Thread t2 = new Thread(()->{ synchronized (loker){ System.out.println("線程二初次獲得對象鎖,執(zhí)行過程中調(diào)用鎖對象的notify()方法~~"); loker.notify(); System.out.println("喚醒線程一前,后面的代碼繼續(xù)執(zhí)行~~"); } System.out.println("線程二結(jié)束"); },"線程二"); t1.start(); //防止t2優(yōu)先獲得CPU執(zhí)行權(quán)而錯過喚醒t1 Thread.sleep(1000); t2.start(); } }
運行結(jié)果(運行流程也就是運行的打印結(jié)果):
例題一
有三個線程,線程名稱分別為:a,b,c。每個線程打印自己的名稱。
需要讓他們同時啟動,并按 c,b,a的順序打印
代碼詳解:
import java.util.*; public class Test { public static Object loker1 = new Object(); public static Object loker2 = new Object(); public static void main(String[] args) { System.out.println("打印順序如下:"); Thread t1 = new Thread(()->{ //為了防止線程A的喚醒沒有被線程B接受,這里先讓線程A睡一會 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+": C"); synchronized (loker1){ loker1.notify(); } },"線程C"); Thread t2 = new Thread(()->{ synchronized (loker1){ //等待線程A的喚醒 try { loker1.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+": B"); //線程B喚醒線程C synchronized (loker2){ loker2.notify(); } },"線程B"); Thread t3 = new Thread(()->{ //線程C等待線程A的喚醒 synchronized (loker2){ try { loker2.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+": A"); },"線程A"); //開啟線程 t1.start(); t2.start(); t3.start(); } }
運行結(jié)果:
例題二
有三個線程,分別只能打印A,B和C
要求按順序打印ABC,打印10次
輸出示例:
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
ABC
大致過程:
代碼詳解:
import java.util.*; public class Demo { private static Object locker1 = new Object(); private static Object locker2 = new Object(); private static Object locker3 = new Object(); public static void main(String[] args) throws InterruptedException { System.out.println("打印結(jié)果:"); Thread t1 = new Thread(() -> { try { for (int i = 0; i < 10; i++) { synchronized (locker1) { locker1.wait(); } System.out.print("A"); synchronized (locker2) { locker2.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t2 = new Thread(() -> { try { for (int i = 0; i < 10; i++) { synchronized (locker2) { locker2.wait(); } System.out.print("B"); synchronized (locker3) { locker3.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t3 = new Thread(() -> { try { for (int i = 0; i < 10; i++) { synchronized (locker3) { locker3.wait(); } System.out.println("C"); synchronized (locker1) { locker1.notify(); } } } catch (InterruptedException e) { e.printStackTrace(); } }); t1.start(); t2.start(); t3.start(); //讓三個線程都拿到鎖 Thread.sleep(1000); // 從線程 t1 啟動 synchronized (locker1) { locker1.notify(); } } }
運行結(jié)果:
四.什么時候釋放鎖——wait()與notify()
由于等待一個鎖定線程只有在獲得這把鎖之后,才能恢復(fù)運行,所以讓持有鎖的線程在不需要鎖的時候及時釋放鎖是很重要的。在以下情況下,持有鎖的線程會釋放鎖:
1.執(zhí)行完同步代碼塊。
2.在執(zhí)行同步代碼塊的過程中,遇到異常而導(dǎo)致線程終止。
3.在執(zhí)行同步代碼塊的過程中,執(zhí)行了鎖所屬對象的wait()方法,這個線程會釋放鎖,進行對象的等待池。
除了以上情況外,只要持有鎖的對象還沒有執(zhí)行完同步代碼塊,就不會釋放鎖。因此在以下情況下,線程不會釋放鎖:
1.在執(zhí)行同步代碼塊的過程中,執(zhí)行了Thread.sleep()方法,當(dāng)前線程放棄CPU,開始睡眠,在睡眠中不會釋放鎖。
2.在執(zhí)行同步代碼塊的過程中,執(zhí)行了Thread.yield()方法,當(dāng)前線程放棄CPU,但不會釋放鎖。
3.在執(zhí)行同步代碼塊的過程中,其他線程執(zhí)行了當(dāng)前對象的suspend()方法,當(dāng)前線程被暫停,但不會釋放鎖。但Thread類的suspend()方法已經(jīng)被廢棄。
到此這篇關(guān)于Java多線程中wait notify等待喚醒機制詳解的文章就介紹到這了,更多相關(guān)Java等待喚醒機制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot配置文件properties和yml的實現(xiàn)
本文主要介紹了SpringBoot配置文件properties和yml的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-04-04Java實現(xiàn)橋接方法isBridge()和合成方法isSynthetic()
本文主要介紹了Java實現(xiàn)橋接方法isBridge()和合成方法isSynthetic(),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06java 使用ConcurrentHashMap和計數(shù)器實現(xiàn)鎖
這篇文章主要介紹了java 使用ConcurrentHashMap和計數(shù)器實現(xiàn)鎖的相關(guān)資料,需要的朋友可以參考下2017-05-05SpringBoot項目使用MDC給日志增加唯一標(biāo)識的實現(xiàn)步驟
本文介紹了如何在SpringBoot項目中使用MDC(Mapped?Diagnostic?Context)為日志增加唯一標(biāo)識,以便于日志追蹤,通過創(chuàng)建日志攔截器、配置攔截器以及修改日志配置文件,可以實現(xiàn)這一功能,文章還提供了源碼地址,方便讀者學(xué)習(xí)和參考,感興趣的朋友一起看看吧2025-03-03Java的wait(), notify()和notifyAll()使用心得
本篇文章是對java的 wait(),notify(),notifyAll()進行了詳細的分析介紹,需要的朋友參考下2013-08-08詳解java中的PropertyChangeSupport與PropertyChangeListener
這篇文章主要介紹了詳解java中的PropertyChangeSupport與PropertyChangeListener的相關(guān)資料,需要的朋友可以參考下2017-09-09