1. 程式人生 > >多執行緒學習筆記二之JUC元件

多執行緒學習筆記二之JUC元件

概述

  為了對共享資源提供更細粒度的同步控制,JDK5新增了java.util.concurrent(JUC)併發工具包,併發包新增了Lock介面(以及相關實現類)用來實現鎖功能,它提供了與synchronized關鍵字相似的同步功能,只是在使用時需要顯式地獲取和釋放鎖,還具備內建鎖不具備的自由操作鎖獲取和釋放、可中斷地獲取鎖以及超時獲取鎖等多種synchronized關鍵字不具備的同步特性。
  為了實現JUC提出的各種功能的鎖,JUC包的作者,併發大師Doug Lee提出了同步器 synchronizer的概念,在同步器中定義了共享資源的同步狀態,維護了一個雙端的先入先出的同步佇列用於存放獲取共享資源失敗而等待的執行緒,執行緒利用同步器實現的鎖獲取共享資源流程如下:

為了實現上述操作,需要下面三個基本元件的相互協作:

  • 對共享資源同步狀態進行原子性管理 ---> 利用CAS對同步狀態進行更新

  • 執行緒的阻塞與喚醒 ---> 呼叫native方法

  • 等待佇列的管理 ---> 維護FIFO佇列

  由此可以看出,同步器是實現鎖的關鍵,同步器面向的是執行緒訪問和資源控制,它定義了執行緒對資源是否能夠獲取以及執行緒的排隊等操作。關於同步器的詳細解釋會在AQS(AbstractQueuedSynchronizer)解析裡給出。

JUC鎖框架圖

  JUC中Lock介面定義了鎖的規範,各種功能的鎖都實現了Lock介面,各個鎖以內部類繼承AQS同步器的方式聚合了同步器,從而以同步器為基石實現具體功能的鎖。

  1. Lock
      Lock介面為獨佔鎖(同一時間共享資源只能由一個執行緒獲取),共享鎖(同一時間共享資源可由多個執行緒獲取),公平鎖(各個執行緒獲得鎖的機會是公平的),非公平鎖(各個執行緒獲得鎖的機會是公平的),重入鎖(執行緒在獲取到鎖之後,再次獲取該鎖而不會被該鎖所阻塞,不會自己把自己鎖在外面)提供了實現規範,介面中定義的方法如下:

  2. AbstractQueuedSynchronizer
      AbstractQueuedSynchronizer就是被稱之為AQS的類,可以用於構建鎖或者其他相關同步裝置的基礎框架。從圖中也可以看出,ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier和Semaphore這些類通過內部類繼承AQS的方式來實現鎖的功能。

  3. Condition
      Condition需要和Lock聯合使用,它的作用是代替Object監視器方法,可以通過await(),signal()來休眠/喚醒執行緒。Condition 介面描述了可能會與鎖有關聯的條件變數。這些變數在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能,介面中定義的方法如下:

  4. LockSurport
      LockSupport中的park() 和 unpark()呼叫native方法將執行緒休眠。

  5. ReentrantLock
      ReentrantLock對與共享資源採取的是較為保守的獨佔策略,即只有一個執行緒能夠獲得鎖;ReentrantLock支援公平鎖和非公平鎖,預設是非公平鎖;從名稱也能看出,ReentrantLock是可重入鎖。

  6. ReentrantReadWriteLock
      ReentrantReadWriteLock是讀寫鎖介面ReadWriteLock的實現類,它包括子類ReadLock和WriteLock。ReentrantLock是共享鎖,而WriteLock是獨佔鎖。

  7. CountDownLatch
      CountDownLatch是一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待。

  8. CyclicBarrier
      CyclicBarrier是一個同步輔助類,允許一組執行緒互相等待,直到到達某個公共屏障點(common barrier point)。因為該barrier在釋放等待執行緒後可以重用,所以稱它為迴圈的barrier。喜歡的朋友可以進圈:609164807 一起交流 一起進步

  9. Semaphore
      Semaphore是一個計數訊號量,它的本質是一個"共享鎖"。訊號量維護了一個訊號量許可集。執行緒可以通過呼叫acquire()來獲取訊號量的許可;當訊號量中有可用的許可時,執行緒能獲取該許可;否則執行緒必須等待,直到有可用的許可為止。 執行緒可以通過release()來釋放它所持有的訊號量許可。

使用內建鎖還是JUC顯示鎖?

  JUC中的顯示鎖提供了與synchronized內建鎖相同的互斥性與記憶體可見性,那麼我們的多執行緒程式碼到底使用哪一種鎖來實現同步呢?首先從效能角度考慮,在JDK5 顯示鎖剛推出時,效能是大幅領先於內建鎖的,在隨後的JDK版本中,JVM對內建鎖進行了效能優化,現在二者的效能已經沒有明顯優劣之分;從功能使用上,內建鎖的使用較為簡單,無需手動獲得以及釋放鎖,而顯示鎖的功能更為強大,具有更高的靈活性,當我們需要使用到鎖的高階功能,如以響應中斷/支援超時的方式獲取鎖或者自定義實現鎖,這時候可以考慮內建鎖。