Java多線程(二)——Java對象的Monitor機制

Java多線程(二)——Java對象的Monitor機制

一、概述

Java虛擬機給每個對象和class字節碼都設置了一個監聽器Monitor,用於檢測併發代碼的重入,同時在Object類中還提供了notify和wait方法來對線程進行控制。

在java.lang.Object類中有如下代碼:

public class Object {
    ...
    private transient int shadow$_monitor_;
    public final native void notify();
    public final native void notifyAll();
    public final native void wait() throws InterruptedException;
    public final void wait(long millis) throws InterruptedException {
        wait(millis, 0);
    }
    public final native void wait(long millis, int nanos) throws InterruptedException;
    ...
}

二、Monitor機制

Monitor的機制如下圖:

這裏寫圖片描述

結合上圖來分析Object的Monitor機制。

Monitor可以類比爲一個特殊的房間,這個房間中有一些被保護的數據,Monitor保證每次只能有一個線程能進入這個房間進行訪問被保護的數據,進入房間即爲持有Monitor,退出房間即爲釋放Monitor。

當一個線程需要訪問受保護的數據(即需要獲取對象的Monitor)時,它會首先在entry-set入口隊列中排隊(這裏並不是真正的按照排隊順序),如果沒有其他線程正在持有對象的Monitor,那麼它會和entry-set隊列和wait-set隊列中的被喚醒的其他線程進行競爭(即通過CPU調度),選出一個線程來獲取對象的Monitor,執行受保護的代碼段,執行完畢後釋放Monitor,如果已經有線程持有對象的Monitor,那麼需要等待其釋放Monitor後再進行競爭。

再說一下wait-set隊列。當一個線程擁有Monitor後,經過某些條件的判斷(比如用戶取錢發現賬戶沒錢),這個時候需要調用Object的wait方法,線程就釋放了Monitor,進入wait-set隊列,等待Object的notify方法(比如用戶向賬戶裏面存錢)。當該對象調用了notify方法或者notifyAll方法後,wait-set中的線程就會被喚醒,然後在wait-set隊列中被喚醒的線程和entry-set隊列中的線程一起通過CPU調度來競爭對象的Monitor,最終只有一個線程能獲取對象的Monitor。

需要注意的是:

  • 當一個線程在wait-set中被喚醒後,並不一定會立刻獲取Monitor,它需要和其他線程去競爭
  • 如果一個線程是從wait-set隊列中喚醒後,獲取到的Monitor,它會去讀取它自己保存的PC計數器中的地址,從它調用wait方法的地方開始執行。

三、Monitor的實現

前面已經分析了Monitor的機制,那麼在Java中是如何實現的呢?

即通過synchronized關鍵字實現線程同步來獲取對象的Monitor。synchronized同步分爲以下兩種方式:

同步代碼塊
synchronized(Obejct obj) {
    //同步代碼塊
    ...
}

上述代碼表示在進入同步代碼塊之前,先要去獲取obj的Monitor,如果已經被其他線程獲取了,那麼當前線程必須等待直至其他線程釋放obj的Monitor

這裏的obj可以是類.class,表示需要去獲取該類的字節碼的Monitor,獲取後,其他線程無法再去獲取到class字節碼的Monitor了,即無法訪問屬於類的同步的靜態方法了,但是對於對象的實例方法的訪問不受影響

同步方法
public class Test {
    public static Test instance;

    public int val;

    public synchronized void set(int val) {
        this.val = val;
    }

    public static synchronized void set(Test instance) {
        Test.instance = instance;
    }

}

上述使用了synchronized分別修飾了非靜態方法和靜態方法。
非靜態方法可以理解爲,需要獲取當前對象this的Monitor,獲取後,其他需要獲取該對象的Monitor的線程會被堵塞。
靜態方法可以理解爲,需要獲取該類字節碼的Monitor(因爲static方法不屬於任何對象,而是屬於類的方法),獲取後,其他需要獲取字節碼的Monitor的線程會被堵塞。

四、Object的notify方法和wait方法詳解

上面在講述Monitor機制的時候已經分析了notify和wait的用法,這裏具體分析下。

wait方法

wait有三個重載方法,分別如下:

wait()
wait(long millis)
wait(long millis, int nanos) 

後面兩個傳入了時間參數(nanos表示納秒),表示如果指定時間過去還沒有其他線程調用notify或者notifyAll方法來將其喚醒,那麼該線程會自動被喚醒。

調用obj.wait方法需要注意的是,當前線程必須獲取到了obj的Monitor,才能去調用其wait方法,即wait必須放在同步方法或同步代碼塊中。(調用的是obj.wait(),而不是Thread.currentThread.wait())

notify方法

notify有兩個方法notify和notifyAll,前者只能喚醒一個正在等待這個對象的monitor的線程,具體由JVM決定,後者則會喚醒所有正在等待這個對象的monitor的線程

需要關注的點:

  • 調用notify方法,並不意味着釋放了Monitor,必須要等同步代碼塊結束後纔會釋放Monitor
  • 在調用notify方法時,必須保證其他線程處於wait狀態,否則調用notify沒有任何效果,導致之後其他線程永遠處於堵塞狀態
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章