多種多線程鎖機制的實現方式與比較

Java提供了多種多線程鎖機制的實現方式,常見的有:

  1.  synchronized
  2.  ReentrantLock
  3.  Semaphore
  4.  AtomicInteger等

每種機制都有優缺點與各自的適用場景,必須熟練掌握他們的特點才能在Java多線程應用開發時得心應手。

4種Java線程鎖(線程同步)

高併發編程系列:4種常用Java線程鎖的特點,性能比較、使用場景

1.synchronized

在Java中synchronized關鍵字被常用於維護數據一致性。

synchronized機制是給共享資源上鎖,只有拿到鎖的線程纔可以訪問共享資源,這樣就可以強制使得對共享資源的訪問都是順序的。

Java開發人員都認識synchronized,使用它來實現多線程的同步操作是非常簡單的,只要在需要同步的對方的方法、類或代碼塊中加入該關鍵字,它能夠保證在同一個時刻最多隻有一個線程執行同一個對象的同步代碼,可保證修飾的代碼在執行過程中不會被其他線程干擾。使用synchronized修飾的代碼具有原子性和可見性,在需要進程同步的程序中使用的頻率非常高,可以滿足一般的進程同步要求。

synchronized (obj) {

//方法

…….

}

synchronized實現的機理依賴於軟件層面上的JVM,因此其性能會隨着Java版本的不斷升級而提高。

到了Java1.6,synchronized進行了很多的優化,有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等,效率有了本質上的提高。在之後推出的Java1.7與1.8中,均對該關鍵字的實現機理做了優化。

需要說明的是,當線程通過synchronized等待鎖時是不能被Thread.interrupt()中斷的,因此程序設計時必須檢查確保合理,否則可能會造成線程死鎖的尷尬境地。

最後,儘管Java實現的鎖機制有很多種,並且有些鎖機制性能也比synchronized高,但還是強烈推薦在多線程應用程序中使用該關鍵字,因爲實現方便,後續工作由JVM來完成,可靠性高。只有在確定鎖機制是當前多線程程序的性能瓶頸時,才考慮使用其他機制,如ReentrantLock等。

2.ReentrantLock

可重入鎖,顧名思義,這個鎖可以被線程多次重複進入進行獲取操作。

ReentantLock繼承接口Lock並實現了接口中定義的方法,除了能完成synchronized所能完成的所有工作外,還提供了諸如可響應中斷鎖、可輪詢鎖請求、定時鎖等避免多線程死鎖的方法。

Lock實現的機理依賴於特殊的CPU指定,可以認爲不受JVM的約束,並可以通過其他語言平臺來完成底層的實現。在併發量較小的多線程應用程序中,ReentrantLock與synchronized性能相差無幾,但在高併發量的條件下,synchronized性能會迅速下降幾十倍,而ReentrantLock的性能卻能依然維持一個水準。

因此我們建議在高併發量情況下使用ReentrantLock。

ReentrantLock引入兩個概念:公平鎖與非公平鎖

公平鎖指的是鎖的分配機制是公平的,通常先對鎖提出獲取請求的線程會先被分配到鎖。反之,JVM按隨機、就近原則分配鎖的機制則稱爲不公平鎖。

ReentrantLock在構造函數中提供了是否公平鎖的初始化方式,默認爲非公平鎖。這是因爲,非公平鎖實際執行的效率要遠遠超出公平鎖,除非程序有特殊需要,否則最常用非公平鎖的分配機制。

ReentrantLock通過方法lock()與unlock()來進行加鎖與解鎖操作,與synchronized會被JVM自動解鎖機制不同,ReentrantLock加鎖後需要手動進行解鎖。爲了避免程序出現異常而無法正常解鎖的情況,使用ReentrantLock必須在finally控制塊中進行解鎖操作。通常使用方式如下所示:

Lock lock = new ReentrantLock();

try {

lock.lock();

//…進行任務操作5 }

finally {

lock.unlock();

}

3.Semaphore

上述兩種鎖機制類型都是“互斥鎖”,學過操作系統的都知道,互斥是進程同步關係的一種特殊情況,相當於只存在一個臨界資源,因此同時最多隻能給一個線程提供服務。但是,在實際複雜的多線程應用程序中,可能存在多個臨界資源,這時候我們可以藉助Semaphore信號量來完成多個臨界資源的訪問。

Semaphore基本能完成ReentrantLock的所有工作,使用方法也與之類似,通過acquire()與release()方法來獲得和釋放臨界資源。

經實測,Semaphone.acquire()方法默認爲可響應中斷鎖,與ReentrantLock.lockInterruptibly()作用效果一致,也就是說在等待臨界資源的過程中可以被Thread.interrupt()方法中斷。

此外,Semaphore也實現了可輪詢的鎖請求與定時鎖的功能,除了方法名tryAcquire與tryLock不同,其使用方法與ReentrantLock幾乎一致。Semaphore也提供了公平與非公平鎖的機制,也可在構造函數中進行設定。

Semaphore的鎖釋放操作也由手動進行,因此與ReentrantLock一樣,爲避免線程因拋出異常而無法正常釋放鎖的情況發生,釋放鎖的操作也必須在finally代碼塊中完成

4.AtomicInteger

首先說明,此處AtomicInteger是一系列相同類的代表之一,常見的還有AtomicLong、AtomicLong等,他們的實現原理相同,區別在與運算對象類型的不同。

我們知道,在多線程程序中,諸如++i

i++等運算不具有原子性,是不安全的線程操作之一。通常我們會使用synchronized將該操作變成一個原子操作,但JVM爲此類操作特意提供了一些同步類,使得使用更方便,且使程序運行效率變得更高。通過相關資料顯示,通常AtomicInteger的性能是ReentantLock的好幾倍。

Java線程鎖總結

1.synchronized:

在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在於,編譯程序通常會儘可能的進行優化synchronize,另外可讀性非常好。

2.ReentrantLock:

在資源競爭不激烈的情形下,性能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的性能一下子能下降好幾十倍,而ReentrantLock確還能維持常態。

高併發量情況下使用ReentrantLock。

3.Atomic:

和上面的類似,不激烈情況下,性能比synchronized略遜,而激烈的時候,也能維持常態。激烈的時候,Atomic的性能會優於ReentrantLock一倍左右。但是其有一個缺點,就是隻能同步一個值,一段代碼中只能出現一個Atomic的變量,多於一個同步無效。因爲他不能在多個Atomic之間同步。

所以,我們寫同步的時候,優先考慮synchronized,如果有特殊需要,再進一步優化。ReentrantLock和Atomic如果用的不好,不僅不能提高性能,還可能帶來災難。

以上就是Java線程鎖的詳解,除了從編程的角度應對高併發,更多還需要從架構設計的層面來應對高併發場景,例如:Redis緩存、CDN、異步消息等,詳細的內容如下。

原文鏈接:http://youzhixueyuan.com/4-kinds-of-java-thread-locks.html

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