首頁 > 軟體

java的Object裡wait()實現原理講解

2021-09-27 13:01:36

Object中的wait()實現原理

在進行wait()之前,就代表著需要爭奪Synchorized,而Synchronized程式碼塊通過javap生成的位元組碼中包含monitorenter和monitorexit兩個指令。

當在進加鎖的時候會執行monitorenter指令,執行該指令可以獲取物件的monitor。同時在執行Lock.wait()的時候也必須持有monitor物件。

在多核環境下,多個執行緒有可能同時執行monitorenter指令,並獲取lock物件關聯的monitor,但只有一個執行緒可以和monitor建立關聯,這個執行緒執行到wait方法時,wait方法會將當前執行緒放入wait set,使其進行等待直到被喚醒,並放棄lock物件上的所有同步宣告,意味著該執行緒釋放了鎖,其他執行緒可以重新執行加鎖操作,notify方法會選擇wait set中任意一個執行緒進行喚醒,notifyAll方法會喚醒monitor的wait set中所有執行緒。執行完notify方法並不會立馬喚醒等待執行緒。那麼wait具體是怎麼實現的呢?

首先在HotSpot虛擬機器器中,monitor採用ObjectMonitor實現,每個執行緒都具有兩個佇列,分別為free和used,用來存放ObjectMonitor。如果當前free列表為空,執行緒將向全域性global list請求分配ObjectMonitor。

ObjectMonitor物件中有兩個佇列,都用來儲存ObjectWaiter物件,分別是_WaitSet 和 _EntrySet。_owner用來指向獲得ObjectMonitor物件的執行緒

ObjectWaiter物件是雙向連結串列結構,儲存了_thread(當前執行緒)以及當前的狀態TState等資料, 每個等待鎖的執行緒都會被封裝成ObjectWaiter物件。

  • _WaitSet:處於wait狀態的執行緒,會被加入到wait set;
  • _EntrySett:處於等待鎖block狀態的執行緒,會被加入到entry set;

wait方法實現

lock.wait()方法最終通過ObjectMonitor的 wait(jlong millis, bool interruptable, TRAPS)實現

1、將當前執行緒封裝成ObjectWaiter物件node

2、通過ObjectMonitor::AddWaiter方法將node新增到_WaitSet列表中

3、通過ObjectMonitor::exit方法釋放當前的ObjectMonitor物件,這樣其它競爭執行緒就可以獲取該ObjectMonitor物件

4、最終底層的park方法會掛起執行緒

ObjectSynchorizer::wait方法通過Object物件找到ObjectMonitor物件來呼叫方法 ObjectMonitor::wait(),通過呼叫ObjectMonitor::AddWaiter()可以把新建的ObjectWaiter物件,放入到_WaitSet佇列的末尾,然後在ObjectMonitor::exit釋放鎖,接著通過執行thread_ParkEvent->park來掛起執行緒,也就是進行wait。

Object物件中的wait,notify,notifyAll的理解

wait,notify,notifyAll 是定義在Object類的實體方法,用於控制執行緒狀態,線上程共同作業時,大家都會用到notify()或者notifyAll()方法,其中wait與notify是java同步機制中重要的組成部分,需要結合與synchronized關鍵字才能使用,在呼叫一個Object的wait與notify/notifyAll的時候,必須保證呼叫程式碼對該Object是同步的,也就是說必須在作用等同於synchronized(object){......}的內部才能夠去呼叫obj的wait與notify/notifyAll三個方法,否則就會報錯:java.lang.IllegalMonitorStateException:current thread not owner(意思是因為沒有同步,所以執行緒對物件鎖的狀態是不確定的,不能呼叫這些方法)。

wait的目的就在於暴露出物件鎖,所以需要保證在lock的同步程式碼中呼叫lock.wait()方法,讓其他執行緒可以通過物件的notify叫醒等待在該物件的等該池裡的執行緒。同樣notify也會釋放物件鎖,在呼叫之前必須獲得物件的鎖,不然也會報異常。所以,線上程自動釋放其佔有的物件鎖後,不會去申請物件鎖,只有當執行緒被喚醒的時候或者達到最大的睡眠時間,它才再次爭取物件鎖的權利

主要方法:

(1).wait()

等待物件的同步鎖,需要獲得該物件的同步鎖才可以呼叫這個方法,否則編譯可以通過,但執行時會收到一個異常:IllegalMonitorStateException。呼叫任意物件的 wait() 方法導致該執行緒阻塞,該執行緒不可繼續執行,並且該物件上的鎖被釋放。

(2).notify()

喚醒在等待該物件同步鎖的執行緒(只喚醒一個,如果有多個在等待),注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,而是由JVM確定喚醒哪個執行緒,而且不是按優先順序。呼叫任意物件的notify()方法則導致因呼叫該物件的 wait()方法而阻塞的執行緒中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。

(3).notifyAll()

喚醒所有等待的執行緒,注意喚醒的是notify之前wait的執行緒,對於notify之後的wait執行緒是沒有效果的。

通過一個範例來看一下實際的效果,開啟兩個線和,一個執行緒 列印1到52的數位,一個列印A到Z的字母,要求,列印兩個數,列印一個字母,這樣交替順序列印,程式碼如下:

/**
 * create by spy on 2018/6/4
 */
public class ShuZiZiMuThread {
    public static void main(String[] args) {
        Object object = new Object();
        shuzi shuzi = new shuzi(object);
        zimu zimu = new zimu(object);
        Thread t = new Thread(shuzi);
        t.setName("shuzi");
        Thread t1 = new Thread(zimu);
        t1.setName("zimu");
        t.start();//數位執行緒先執行
        t1.start();
    }
}
class shuzi implements Runnable{
    private Object object;
    //宣告類的參照
    public shuzi(Object object) {
        this.object = object;
    }
    public void run() {
        synchronized (object) {//上鎖
            for(int i=1;i<53;i++){
                System.out.print(i+",");
                if(i%2==0){
                    object.notifyAll();//喚醒其它爭奪許可權的執行緒
                    try {
                        object.wait();//釋放鎖,進入等待
                        System.out.println("數位列印類打全列印當前物件擁有物件鎖的執行緒"+Thread.currentThread().getName());//輸出當前擁有鎖的執行緒名稱
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
class zimu implements Runnable{
    private Object object;
    public zimu(Object object) {
        this.object = object;
    }
    public void run() {
        synchronized (object) {
            for(int j=65;j<91;j++){
                char c = (char)j;
                System.out.print(c);
                object.notifyAll();//喚醒其它爭奪許可權的執行緒
                try {
                    object.wait();//釋放鎖,進入等待
                    System.out.println("字母列印類打全列印當前物件擁有物件鎖的執行緒"+Thread.currentThread().getName());//輸出當前擁有鎖的執行緒名稱
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

實際執行的結果 :

1,2,A數位列印類列印當前物件擁有物件鎖的執行緒shuzi
3,4,字母列印類列印當前物件擁有物件鎖的執行緒zimu
B數位列印類列印當前物件擁有物件鎖的執行緒shuzi
5,6,字母列印類列印當前物件擁有物件鎖的執行緒zimu
C數位列印類列印當前物件擁有物件鎖的執行緒shuzi
7,8,字母列印類列印當前物件擁有物件鎖的執行緒zimu
D數位列印類列印當前物件擁有物件鎖的執行緒shuzi
9,10,字母列印類列印當前物件擁有物件鎖的執行緒zimu
E數位列印類列印當前物件擁有物件鎖的執行緒shuzi
11,12,字母列印類列印當前物件擁有物件鎖的執行緒zimu
F數位列印類列印當前物件擁有物件鎖的執行緒shuzi
13,14,字母列印類列印當前物件擁有物件鎖的執行緒zimu
G數位列印類列印當前物件擁有物件鎖的執行緒shuzi
15,16,字母列印類列印當前物件擁有物件鎖的執行緒zimu
H數位列印類列印當前物件擁有物件鎖的執行緒shuzi
17,18,字母列印類列印當前物件擁有物件鎖的執行緒zimu
I數位列印類列印當前物件擁有物件鎖的執行緒shuzi
19,20,字母列印類列印當前物件擁有物件鎖的執行緒zimu
J數位列印類列印當前物件擁有物件鎖的執行緒shuzi
21,22,字母列印類列印當前物件擁有物件鎖的執行緒zimu
K數位列印類列印當前物件擁有物件鎖的執行緒shuzi
23,24,字母列印類列印當前物件擁有物件鎖的執行緒zimu
L數位列印類列印當前物件擁有物件鎖的執行緒shuzi
25,26,字母列印類列印當前物件擁有物件鎖的執行緒zimu
M數位列印類列印當前物件擁有物件鎖的執行緒shuzi
27,28,字母列印類列印當前物件擁有物件鎖的執行緒zimu
N數位列印類列印當前物件擁有物件鎖的執行緒shuzi
29,30,字母列印類列印當前物件擁有物件鎖的執行緒zimu
O數位列印類列印當前物件擁有物件鎖的執行緒shuzi
31,32,字母列印類列印當前物件擁有物件鎖的執行緒zimu
P數位列印類列印當前物件擁有物件鎖的執行緒shuzi
33,34,字母列印類列印當前物件擁有物件鎖的執行緒zimu
Q數位列印類列印當前物件擁有物件鎖的執行緒shuzi
35,36,字母列印類列印當前物件擁有物件鎖的執行緒zimu
R數位列印類列印當前物件擁有物件鎖的執行緒shuzi
37,38,字母列印類列印當前物件擁有物件鎖的執行緒zimu
S數位列印類列印當前物件擁有物件鎖的執行緒shuzi
39,40,字母列印類列印當前物件擁有物件鎖的執行緒zimu
T數位列印類列印當前物件擁有物件鎖的執行緒shuzi
41,42,字母列印類列印當前物件擁有物件鎖的執行緒zimu
U數位列印類列印當前物件擁有物件鎖的執行緒shuzi
43,44,字母列印類列印當前物件擁有物件鎖的執行緒zimu
V數位列印類列印當前物件擁有物件鎖的執行緒shuzi
45,46,字母列印類列印當前物件擁有物件鎖的執行緒zimu
W數位列印類列印當前物件擁有物件鎖的執行緒shuzi
47,48,字母列印類列印當前物件擁有物件鎖的執行緒zimu
X數位列印類列印當前物件擁有物件鎖的執行緒shuzi
49,50,字母列印類列印當前物件擁有物件鎖的執行緒zimu
Y數位列印類列印當前物件擁有物件鎖的執行緒shuzi
51,52,字母列印類列印當前物件擁有物件鎖的執行緒zimu
Z數位列印類列印當前物件擁有物件鎖的執行緒shuzi

結果分析:

通過結果可以看出:

在字母打一列印類裡 呼叫完

object.notifyAll();//喚醒其它爭奪許可權的執行緒
object.wait();//釋放鎖,進入等待後,擁有物件鎖的執行緒是shuzi在數位列印類裡 呼叫完
object.notifyAll();//喚醒其它爭奪許可權的執行緒
object.wait();//釋放鎖,進入等待後,擁有物件鎖的執行緒是zimu

以上為個人經驗,希望能給大家一個參考,也希望大家多多支援it145.com。


IT145.com E-mail:sddin#qq.com