synchronized鎖自旋

http://www.jianshu.com/p/5dbb07c8d5d5數據結構

原理

一般說的synchronized在方法或塊上加鎖,這裏的鎖就是對象鎖(固然也能夠在類上面),或者叫重量鎖,在JVM中又叫對象監視器(Monitor),就是對象來監視線程的互斥。多線程

先來回顧一下對象在堆裏的邏輯結構:jvm


 

對象在內存中的結構看這裏》》線程

對象頭裏的結構大體如此:對象


 

其中Tag的2bit用來顯示鎖類型。一般咱們說synchronized的對象鎖,就是這裏Tag=10時的monitor對象,這裏的Monitor address就是這個monitor對象(就是重量鎖)的地址。blog

當多個線程同時請求synchronized方法或塊時,monitor會設置幾個虛擬邏輯數據結構來管理這些多線程。下圖是簡化了的管理結構。隊列


 

新請求的線程會首先被加入到線程排隊隊列中,線程阻塞,當某個擁有鎖的線程unlock以後,則排隊隊列裏的線程競爭上崗(synchronized是不公平競爭鎖,下面還會講到)。若是運行的線程調用對象的wait()後就釋放鎖並進入wait線程集合那邊,當調用對象的notify()或notifyall()後,wait線程就到排隊那邊。這是大體的邏輯。ip

同時再看看線程的狀態圖內存


 

Blocked就是阻塞狀態。資源

wait()和sleep()最大的不一樣在於wait()會釋放對象鎖,而sleep()不會!wait、sleep、yield區別以下:


 

彷佛講到這裏,synchronized鎖和wait()、notify()來實現多線程同步就完成了。

可是,自旋鎖或自適應自旋鎖:

由於線程阻塞後進入排隊隊列和喚醒都須要CPU從用戶態轉爲核心態,尤爲頻繁的阻塞和喚醒對CPU來講是負荷很重的工做。同時統計發現,不少對象鎖的鎖定狀態只會持續很短的一段時間,例如一個線程切換週期,這樣的話在很短的時間內阻塞線程又很快喚醒線程顯然不值得,因此引入了自旋鎖概念。

所謂「自旋」,就monitor並不把線程阻塞放入排隊隊列,而是去執行一個無心義的循環,循環結束後看看是否鎖已釋放並直接進行競爭上崗步驟,若是競爭不到繼續自旋循環,循環過程當中線程的狀態一直處於running狀態。明顯自旋鎖使得synchronized的對象鎖方式在線程之間引入了不公平。可是這樣能夠保證大吞吐率和執行效率。

不過雖然自旋鎖方式省去了阻塞線程的時間和空間(隊列的維護等)開銷,可是長時間自旋也是很低效的。因此自旋的次數通常控制在一個範圍內,例如10,50等,在超出這個範圍後,線程就進入排隊隊列。

自適應自旋鎖,就是自旋的次數是經過JVM在運行時收集的統計信息,動態調整自旋鎖的自旋次數上界。

講到這裏彷佛synchronized鎖的過程更加豐滿了。

不過synchronized在運行過程當中不是一會兒就到對象鎖這個級別的,它根據線程競爭狀況會通過幾回升級變化。這裏就出現了另外幾種鎖。

輕量鎖和偏向鎖

當多線程環境進入synchronized區域的線程沒競爭時,JVM並不會立刻建立對象鎖,而是用輕量鎖或偏向鎖。

不過須要明確的是,輕量鎖和偏向鎖,都不能代替重量鎖,只不過是在沒有多線程競爭時,不必用重量鎖而無畏的消耗資源。可是一旦出現了多線程競爭時,synchronized區域的輕量鎖或偏向鎖都會當即升級爲重量鎖。

輕量鎖或偏向鎖使用的條件是進入synchronized區域時沒有其餘任何其餘線程在使用。

這時線程t訪問對象的synchronized區域時,對象頭的標誌位Tag狀態爲01,以及還有1位的偏向信息用於記錄這個對象是否可用偏向鎖。而後t在對象上申請輕量鎖時,若偏向信息爲0,代表當前對象還未加鎖,或加過偏向鎖(加過,注意是加過偏向鎖的對象只能被一樣的線程加鎖,若是不一樣的線程想要獲取鎖,須要先將偏向鎖升級爲輕量鎖,稍後會講到),在判斷對當前對象確實沒有被任何其餘線程鎖住後,便可以在該對象上加輕量鎖。

加輕量鎖的過程很簡單:在當前線程的棧幀(stack frame)中生成一個鎖記錄(lock record),這個鎖記錄比前面說的那個對象鎖(管理線程隊列的monitor)簡單多了,它只是對象頭的一個拷貝。而後把對象頭裏的tag改爲00,並把這個棧幀裏的lock record地址放入對象頭裏。若操做成功,那就完成了輕量鎖操做。若是不成功,說明有線程在競爭,則須要在當前對象上生成重量鎖來進行多線程同步,而後將Tag狀態改成10,並生成Monitor對象(重量鎖對象),對象頭裏也會放入Monitor對象的地址。最後將當前線程t排隊隊列中。

輕量鎖的解鎖過程也很簡單就是把棧幀裏剛纔的那個lock record拷貝到對象頭裏,若替換成功,則解鎖完成,若替換不成功,表示在當前線程持有鎖的這段時間內,其餘線程也競爭過鎖,而且發生了鎖升級爲重量鎖,這時須要去Monitor的等待隊列中喚醒一個線程去從新競爭鎖。

偏向鎖是比輕量鎖還輕量的鎖機制。當synchronized區域長期都由同一個線程加鎖、解鎖時,jvm就用偏向鎖來作,它的加鎖解鎖比輕量鎖操做起來指令更加簡化。不過一旦有其餘線程使用synchronized區域,即便沒有線程間競爭,也會把偏向鎖升級爲輕量鎖,固然若是發生線程競爭就再升級爲對象鎖。

鎖的公平與不公平:公平鎖是指線程得到鎖的順序按照fifo的原則,先排隊的先得。非公平鎖指每一個線程都先要競爭鎖,無論排隊前後,因此後到的線程有可能無需進入等待隊列直接競爭到鎖。

非公平鎖雖然可能致使某些線程飢餓,可是鎖的吞吐率是公平鎖好幾倍,synchronized是一個典型的非公平鎖方案,並且無法作成公平鎖。



文/hexter(簡書做者) 原文連接:http://www.jianshu.com/p/5dbb07c8d5d5 著做權歸做者全部,轉載請聯繫做者得到受權,並標註「簡書做者」。
相關文章
相關標籤/搜索