天天看點

【多線程】Synchronized的優化

對synchronized不太了解的同學,可以先參考我的另外一篇文章​​【多線程】淺說Synchronized​​

早期版本synchronized性能較低的原因

在早期版本中,synchronized是一種重量級鎖,其底層由Monitor實作,而Monitor又依賴于作業系統的Mutex Lock。線程擷取到鎖後,需要切換狀态,而作業系統在實作線程的切換時,需要從使用者态轉為核心态,這是一個非常耗時,非常重的操作。是以在之前,synchronized是一種重量級鎖。

JDK1.6之後對synchronized的優化

現在的synchronized已經沒有之前那麼笨重了,在虛拟機層面,對synchronized做了較大的優化,引入了自旋鎖、适應性自旋鎖、鎖消除、鎖粗化,可以減少鎖操作的開銷。

自旋鎖

有時候,擷取到鎖的線程執行的操作耗時極短,為了這麼點微不足道的時間,将接下來等待鎖的線程挂起非常的不值得。挂起線程的操作需要在核心态完成,從使用者态切換到核心态,耗時比較嚴重。

是以現在增加這麼一樣操作,讓等待鎖的線程執行忙循環等待,不停地去嘗試擷取鎖,像一種自旋的操作,故稱之為自旋鎖。

如果之前線程占有鎖的時間極短,那麼自旋鎖的性能将非常的好。但若是占有鎖的時間較長,那麼自旋鎖将白白消耗CPU的資源,在自旋次數到了之後,将會被挂起。

在jdk1.4的時候,自旋鎖預設關閉;jdk1.6之後,自旋鎖預設開啟,預設自旋10次,當然也可以使用PreBlockSpin來修改自旋次數。

自旋鎖的痛點在于:無法在不同場景中,确定出一個可靠的自旋次數。是以,衍生出來适應性自旋鎖。

适應性自旋鎖

在适應性自旋鎖中,自旋的次數不再固定,一般由之前自旋的次數和鎖持有者的狀态決定。

如果在一個鎖對象上,之前的線程都能通過自旋來擷取到鎖,并且沒有超過自旋次數,那麼虛拟機認為,通過自旋擷取到鎖的機率很大,下一次會增加自旋的次數。相反的,如果之前很少有線程通過自旋擷取到鎖,那麼虛拟機會減少自旋的次數,減少到一定次數後,甚至會直接放棄自旋,更新為重量級鎖。

可以看出,适應性自旋鎖十分機智。

鎖消除

從字面意思上可以看出,這是一種直接去除鎖的方法,簡單粗暴。

對于那些根本不可能存在鎖競争卻又包含鎖的情況,虛拟機會直接消除這個鎖,避免無意義的鎖請求。比如我在純單線程中對某個方法或者變量加鎖,或者調用内部實作有鎖的對象(Vector、StringBuffer與HashTable等),虛拟機會直接消除毫無意義的加鎖。

鎖粗化

在上一文中​​【多線程】淺說Synchronized​​,我們談到了synchronized的應用-雙重檢驗鎖的優化過程,強調将加鎖的範圍盡量限制得小一些,直到存在鎖競争的實際區域才加鎖,這樣程式運作更加高效。

但是,如果存在這樣的一種情況:反複的對同一個對象執行加鎖解鎖的操作,也會導緻CPU資源的過度消耗。

鎖粗化,就是将反複的加鎖操作粗化成一個範圍更大的鎖,這樣加鎖隻有一次。

例如,在循環内部,調用StringBuffer的append操作(關于StringBuffer,可以參考我的另外一篇文章​​【JAVA】String、StringBuilder、StringBuffer三者的差別​​),每次append都需要加鎖,虛拟機檢測到這種情況後,首先會對append脫鎖,然後進行鎖粗化,将鎖的範圍擴大到循環外部。

鎖的狀态

鎖的狀态有以下幾種:

  1. 無鎖狀态
  2. 偏向鎖狀态
  3. 輕量級鎖狀态
  4. 重量級鎖狀态

其中,無鎖狀态對應于鎖消除,Monitor對應于重量級鎖,也就是1.6之前的synchronized。

偏向鎖

偏向鎖的核心要義就展現在“偏”字上,這個鎖偏向第一個擷取到它的線程。

在大部分情況下,不存在激烈的鎖競争,總是由同一個線程擷取到該鎖。那麼為了減少同一個線程擷取鎖帶來的開銷,就引入了偏向鎖。

如果一個線程不斷的擷取到了鎖,那麼該鎖就進入偏向鎖狀态。當這個線程再次請求鎖時,無需做任何同步操作,直接擷取到鎖。

當然,偏向鎖适用于基本無鎖競争的情況,當鎖競争激烈時,偏向鎖就失去了作用,會更新為輕量級鎖。

輕量級鎖

在偏向鎖的狀态下,此時又出現了一個線程,與偏向線程競争該鎖,此時該鎖會更新為輕量級鎖。

舉個例子,比如建立一個線程1執行同步print()方法列印奇數,這時候的鎖狀态為偏向鎖。此時,再建立一個線程2同樣執行同步print()方法列印偶數,偏向鎖就會更新為輕量級鎖。線程1列印某個奇數時,線程2并沒有被挂起,而是處于一種自旋狀态,這種自旋效率很高。可是,當我再建立100個線程時,同樣執行同步print()方法,自旋的效率将會變得十分低下,此時輕量級鎖會更新為重量級鎖,即使用Monitor來進行同步。

繼續閱讀