Java:Synchronized和lock鎖分析

Synchronized有什麼用

在多線程併發執行過程中,如果對某個公用變量的操作需要做到單線程,那麼就需要鎖來保證多線程環境下的某個操作是順序執行。

如何實現的

synchronized首先是一個悲觀鎖,支持的同步方法和同步語句都是使用monitor來實現的。Monitor可以理解爲一個同步工具或一種同步機制,通常被描述爲一個對象。每一個Java對象就有一把看不見的鎖,稱爲內部鎖或者Monitor鎖。Monitor是線程私有的數據結構,每一個線程都有一個可用monitor record列表,同時還有一個全局的可用列表。每一個被鎖住的對象都會和一個monitor關聯,java對象頭Mark Word中有指向Monitor對象(存儲的指針的指向),叫做 Lock word,Synchronized鎖便是通過這種方式獲取鎖的,也是爲什麼Java中任意對象可以作爲鎖的原因,同時notify/notifyAll/wait等方法會使用到Monitor鎖對象,所以必須在同步代碼塊中使用。

同時monitor中有一個Owner字段存放擁有該鎖的線程的唯一標識,表示該鎖被這個線程佔用。synchronized通過Monitor來實現線程同步,Monitor是依賴於底層的操作系統的Mutex Lock(互斥鎖,也叫重量級鎖)來實現的線程同步。
在執行流程角度來看,每個對象都與一個monitor相關聯,當一個線程執行到一個monitor監視下的代碼塊中的第一個指令時,該線程必須在引用的對象上獲得一個鎖,這個鎖是monitor實現的。在HotSpot虛擬機中,monitor是由ObjectMonitor實現,使用C++編寫實現,具體代碼在HotSpot虛擬機源碼ObjectMonitor.hpp文件中。

Synchronized鎖範圍

  • 普通同步方法,鎖是當前實例對象
    同步方法依靠的是方法修飾符上的ACC_SYNCHRONIZED標記符隱式實現
	public synchronized void test(){
        // TODO
    }

    public void test(){
        synchronized (this) {
            // TODO
        }
    }
  • 靜態同步方法,鎖是當前類的class對象
    public static synchronized void test(){
        // TODO
    }

    public static void test(){
        synchronized (TestSynchronized.class) {
            // TODO
        }
    }
  • 同步方法塊,鎖是括號裏面的對象
    同步代碼塊是使用monitorenter和monitorexit指令顯式實現的

Synchronized:偏向鎖、輕量級鎖、重量級鎖

這三種鎖是指鎖的狀態,並且是針對Synchronized。在Java 5通過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是通過對象監視器在對象頭中的字段來表明的。

  • 1、偏向鎖
    偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。線程是不需要觸發同步的,減少加鎖/解鎖的一些CAS操作(比如等待隊列的一些CAS操作),降低獲取鎖的代價。偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程纔會釋放鎖,線程不會主動釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態。撤銷偏向鎖後恢復到無鎖(標誌位爲“01”)或輕量級鎖(標誌位爲“00”)的狀態。
    通過-XX:-UseBiasedLocking取消偏向鎖,偏向鎖原本是一種鎖優化,讓同一個線程進來不需要同步操作,所以在鎖競爭少的情況下有優化作用,但多線程 競爭非常激烈的環境下,因爲大量的競爭會導致持有鎖的線程不停地切換,鎖也很難一直保持偏向模式,此時,使用偏向鎖不僅不能優化程序,反而有可能降低程序性能,所以通過此參數取消偏向鎖。偏向鎖通過對比Mark Word解決加鎖問題,避免執行CAS操作。

  • 2、輕量級鎖:標誌位爲“00”
    輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能,自旋到一定程度,升級到重量級鎖。
    爲什麼可以減少時間呢?阻塞或喚醒一個Java線程需要操作系統切換CPU狀態來完成,這種狀態轉換需要耗費處理器時間。在許多場景中,同步資源的鎖定時間很短,爲了這一小段時間去切換線程,線程掛起和恢復現場的花費可能會讓系統得不償失。如果物理機器有多個處理器,能夠讓兩個或以上的線程同時並行執行,我們就可以讓後面那個請求鎖的線程不放棄CPU的執行時間,看看持有鎖的線程是否很快就會釋放鎖。而爲了讓當前線程“稍等一下”,我們需讓當前線程進行自旋,如果在自旋完成後前面鎖定同步資源的線程已經釋放了鎖,那麼當前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷。這就是自旋鎖。自旋超過了限定次數(默認是10次,可以使用-XX:PreBlockSpin來更改)沒有成功獲得鎖,就應當掛起線程。
    缺點同樣也有:線程佔用資源較長,那麼自旋就會消耗cpu時間片,這段時間cpu啥都沒幹,就不停的被你佔用自旋。所以後面提出來自適應自旋鎖,實際效果中自旋鎖很少成功,少量是成功的,輕量級鎖是通過用CAS操作和自旋來解決加鎖問題,避免線程阻塞和喚醒而影響性能

  • 3、重量級鎖:標誌位爲“10”
    重量級鎖是指當鎖爲輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹爲重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。

    Java面試Offer直通車

跟lock的區別

兩者區別:

  • 1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;

  • 2.synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;

  • 3.synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;

  • 4.用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;

  • 5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)

  • 6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。

參考博客

java對一個對象加鎖,鎖的是什麼東西?
美團Java瑣事

深入分析Synchronized原理(阿里面試題)
深入分析synchronized的實現原理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章