4種經常使用Java線程鎖的特色,性能比較及使用場景

 

多個線程同時對同一個對象進行讀寫操做,很容易會出現一些難以預料的問題。因此不少時候咱們須要給代碼塊加鎖,同一時刻只容許一個線程對某個對象進行操做。多線程之因此會容易引起一些難以發現的bug,不少時候是寫代碼的程序員對線程鎖不熟悉或者乾脆就沒有在必要的地方給線程加鎖致使的。本篇我想分享java多線程中的4種常見線程鎖的特色、性能比較及使用場景。java

 

1、多線程的原因程序員

 

在出現了進程以後,操做系統的性能獲得了大大的提高。雖然進程的出現解決了操做系統的併發問題,可是人們仍然不知足,人們逐漸對實時性有了要求。使用多線程的理由之一是和進程相比,它是一種很是花銷小,切換快,更"節儉"的多任務操做方式。編程

在Linux系統下,啓動一個新的進程必須分配給它獨立的地址空間,創建衆多的數據表來維護它的代碼段、堆棧段和數據段,這是一種"昂貴"的多任務工做方式。而在進程中的同時運行多個線程,它們彼此之間使用相同的地址空間,共享大部分數據,啓動一個線程所花費的空間遠遠小於啓動一個進程所花費的空間,並且,線程間彼此切換所需的時間也遠遠小於進程間切換所須要的時間。緩存

 

 

2、多線程併發面臨的問題安全

 

 

因爲多個線程是共同佔有所屬進程的資源和地址空間的,那麼就會存在一個問題:多線程

若是多個線程要同時訪問某個資源,怎麼處理?架構

在Java併發編程中,常常遇到多個線程訪問同一個 共享資源 ,這時候做爲開發者必須考慮如何維護數據一致性,這就是Java鎖機制(同步問題)的來源。併發

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

  1. synchronized函數

  2. ReentrantLock

  3. Semaphore

  4. AtomicInteger等

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

 

3、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的好幾倍。

 

 

4、多線程加鎖總結

 

1.synchronized

在資源競爭不是很激烈的狀況下,偶爾會有同步的情形下,synchronized是很合適的。緣由在於,編譯程序一般會盡量的進行優化synchronize,另外可讀性很是好。

 

2.ReentrantLock

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

高併發量狀況下使用ReentrantLock。

 

3.Atomic

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

因此,咱們寫同步的時候,優先考慮synchronized,若是有特殊須要,再進一步優化。ReentrantLock和Atomic若是用的很差,不只不能提升性能,還可能帶來災難。

 

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

 

以爲不錯點贊支持下

相關文章
相關標籤/搜索