Java線程同步機制

線程同步機制是一套用於協調線程間的數據訪問及活動的機制,該機制用於保障線程安全以及實現這些線程的共同目標。

鎖概述

線程安全問題的前提是多個線程併發訪問共享變量。針對這個情況,將多個線程對共享變量的併發訪問轉換爲串行訪問,既一個共享數據同時只能有一個線程訪問,該線程訪問結束後其他線程才能訪問。這個思想的具體實現就是鎖。
一個線程在訪問共享數據時必須申請對應的鎖,獲得鎖以後才能訪問共享數據,訪問完共享數據後釋放鎖,其他線程才能繼續申請鎖。執行線程在獲取鎖後到釋放鎖之前的這段時間執行的代碼被稱爲臨界區。臨界區一次只能被一個線程訪問執行。
鎖具有排他性,既一個鎖同時刻只能被一個線程持有。這種鎖被稱爲互斥鎖或者排他鎖。這是最常見的鎖。
按照Java虛擬機對鎖的實現方式劃分,分爲內部鎖和顯式鎖。內部鎖是synchronized;顯示鎖是指java.util.concurrent.locks.Lock接口的實現類(如 java.util.concurrent.locks.ReentrantLock類)。
在這裏插入圖片描述

鎖的作用

鎖能夠保護共享數據以實現線程安全,作用包括保障原子性、保障可見性和保障有序性。
鎖通過互斥性保障原子性,互斥就是指一個鎖一次只能被一個線程持有,也就意味着臨界區代碼同一時刻只能被持鎖線程執行。因此,持鎖線程執行臨界區期間沒有其他線程能夠訪問相應的共享數據,也就具備了原子性。
可見性的保障是通過寫線程沖刷處理器緩存和讀線程刷新處理器緩存這兩個動作實現的。Java平臺的鎖獲得後在執行臨界區代碼前可以將寫線程對共享變量所做的更新同步到該線程執行處理器的高速緩存中;鎖釋放時會執行沖刷處理器緩存,這使得寫線程對共享變量所做的更新能夠被“推送”到該線程執行處理器的高速緩存中,從而對讀線程可同步。

鎖對可見性、原子性和有序性的保證是有條件的,需要滿足以下亮點:

  • 這些線程在訪問同一組共享變量的時候必須使用同一個鎖。
  • 這些線程即便是對共享變量只讀時也必須獲取鎖。

上邊兩條有一個不滿足都會使原子性、可見性和有序性沒有保障。

鎖相關的幾個概念

1. 可重入性

下圖是個僞代碼,調用methodA方法時申請到lock鎖,然後臨界區代碼中調用methodB方法,methodB方法也使用lock進行加鎖。這時候就有一個問題啦:methodA的執行線程持有lock鎖的時候調用methodB,那麼methodB執行的時候又去申請鎖lock,而lock此時正被當前線程持有(未被釋放)。那麼,此時methodB究竟能否獲得lock呢?可重入性就是講的這個問題。
可重入性僞代碼

2. 鎖的爭用與調度

Java鎖的調度分爲公平策略和非公平策略,相應的鎖稱爲公平鎖和非公平鎖。內部鎖是非公平鎖,顯示鎖既支持公平鎖也支持非公平鎖。

3. 鎖的粒度

一個鎖所保護的共享數據大小稱爲鎖的粒度。
鎖的粒度過大會導致無所謂的等待。
鎖的粒度過小會增加調度的開銷。

鎖的開銷

鎖的開銷主要包括申請鎖和釋放鎖,以及鎖競爭引發的上下文切換的開銷。這些開銷主要是處理器時間。
鎖可能導致上下文切換,多個線程爭用排他性資源可能導致上下文切換。因此,鎖作爲一種排他性資源,一旦爭用就可能導致上下文切換。

上下文切換

簡單解釋下上下文切換,上下文切換是指線程在時間片用完或者其自身的原因(比如,他需要稍後在繼續運行)暫停其運行時,另一個線程可以被操作系統(線程調度器)選中佔用處理器開始或者繼續其運行。這種一個線程被暫停,即被剝奪處理器的使用權,另一個線程被選中開始或者繼續運行的過程就叫做線程上下文切換。

這裏的上下文是指計算的中間結果以及執行到哪條指令。其實這很類似我們打電話,說到一半來了另外一個重要電話,

內部鎖:synchronized關鍵字

Java平臺每個類都有個一個內部鎖,內部鎖是一種排他鎖,能夠保證原子性、可見性和順序性。
內部鎖是通過synchronized關鍵字實現的,synchronized可以給方法和代碼塊加鎖。
語法如下:
在這裏插入圖片描述
鎖句柄是一個對象的引用,可以寫this表示當前對象,我們習慣稱爲鎖句柄爲鎖。
作爲鎖句柄的變量一般要求final修飾。因爲如果不實用final修飾可能這個變量會被修改,導致多個線程使用的鎖句柄不一致,導致這個鎖失效。有鑑於此,也會用private修飾。
一個線程訪問synchronized修飾的方法或者代碼塊時,jvm會代爲嘗試申請鎖,獲取鎖後執行完臨界區代碼或者出現異常時jvm也會自動釋放鎖,避免出現鎖泄漏的問題。這也就是被稱爲內部鎖的原因。

內部鎖的調度

jvm會爲每個內部鎖分配一個入口集,用於存儲等待獲取相應內部鎖的線程。多個線程競爭一個內部鎖的時候,只有一個線程能得到鎖,剩下的線程就會存儲在入口集中。這個內部鎖被釋放後,入口集中的任意一個線程會被jvm喚醒,從而得到再次申請鎖的機會。由於內部鎖僅支持非公平鎖,所以如果這時候有個不在入口集的活躍線程也去競爭這個鎖,入口集被喚醒的線程與不在入口集的活躍線程會競爭這個內部鎖,都有可能拿到這個鎖。具體實現是當一個不在入口集的活躍線程想獲取鎖時,先試圖插隊,如果佔用鎖的線程釋放了鎖,被喚醒的線程還沒來得及拿鎖,那麼不在入口集的活躍線程就可以直接獲取鎖;如果鎖被其他線程佔用,那就進入口集,和其他入口集線程等待喚醒再次爭奪鎖。
jvm從一個內部鎖的入口集中選擇一個等待線程,作爲下一個可以參與再次申請內部鎖的線程與jvm的具體實現有關:這個被選中的線程可能是入口集中等待時間最長的線程,也可能是等待時間最短的線程,或者完全是隨機的一個線程。因爲不能依賴這個具體的選擇算法。

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