#鎖類型 鎖根據其特性可以劃分出各類各樣的鎖類型,該文主要介紹如下鎖的做用及特性html
- 樂觀鎖/悲觀鎖
- 獨享鎖/共享鎖
- 互斥鎖/讀寫鎖
- 可重入鎖
- 公平鎖/非公平鎖
- 分段鎖
- 偏向鎖/輕量級鎖/重量級鎖
- 自旋鎖
##樂觀鎖/悲觀鎖 樂觀鎖與悲觀鎖並非特指某兩種類型的鎖,是人們定義出來的概念或思想,主要是指看待併發同步的角度。java
樂觀鎖:顧名思義,就是很樂觀,每次去拿數據的時候都認爲別人不會修改,因此不會上鎖,可是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可使用版本號等機制。樂觀鎖適用於多讀的應用類型,這樣能夠提升吞吐量,在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS(Compare and Swap 比較並交換,屬於計算機底層的CPU原子操做)實現的。算法
悲觀鎖:老是假設最壞的狀況,每次去拿數據的時候都認爲別人會修改,因此每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖。好比Java裏面的同步原語synchronized關鍵字的實現就是悲觀鎖。編程
悲觀鎖適合寫操做很是多的場景,樂觀鎖適合讀操做很是多的場景,不加鎖會帶來大量的性能提高。數組
樂觀鎖在Java中的使用,是無鎖編程,經常採用的是CAS算法,典型的例子就是java.util.concurrent.atomic包下的原子類,經過CAS自旋實現原子操做的更新。多線程
悲觀鎖在Java中的體現主要有Synchronized關鍵字和ReentrantLock類。併發
##獨享鎖/共享鎖框架
<span color='red'>獨享鎖是指該鎖一次只能被一個線程所持有。</span>函數
<span color='red'>共享鎖是指該鎖可被多個線程所持有。</span>性能
對於Java ReentrantLock而言,其是獨享鎖。可是對於Lock的另外一個實現類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨享鎖。
讀鎖的共享鎖可保證併發讀是很是高效的,讀寫,寫讀,寫寫的過程是互斥的。
獨享鎖與共享鎖也是經過AQS(AbstractQuenedSynchronizer抽象的隊列式同步器)來實現的,經過實現不一樣的方法,來實現獨享或者共享。
對於Synchronized而言,固然是獨享鎖。
##互斥鎖/讀寫鎖
上面講的獨享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實現。
互斥鎖在Java中的具體實現就是ReentrantLock。
讀寫鎖在Java中的具體實現就是ReadWriteLock。
##可重入鎖 可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖。說的有點抽象,下面會有一個代碼的示例。
對於Java ReetrantLock而言,從名字就能夠看出是一個重入鎖,其名字是Re entrant Lock 從新進入鎖。
對於Synchronized而言,也是一個可重入鎖。可重入鎖的一個好處是可必定程度避免死鎖。
synchronized void setA() throws Exception{ Thread.sleep(1000); setB(); } synchronized void setB() throws Exception{ Thread.sleep(1000); }
##公平鎖/非公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個線程獲取鎖的順序並非按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會形成優先級反轉或者飢餓現象。
對於Java ReetrantLock而言,經過構造函數指定該鎖是不是公平鎖,默認是非公平鎖。非公平鎖的優勢在於吞吐量比公平鎖大。
對於Synchronized而言,也是一種非公平鎖。因爲其並不像ReentrantLock是經過AQS的來實現線程調度,因此並無任何辦法使其變成公平鎖。
##分段鎖
分段鎖實際上是一種鎖的設計,並非具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是經過分段鎖的形式來實現高效的併發操做。
咱們以ConcurrentHashMap來講一下分段鎖的含義以及設計思想,ConcurrentHashMap中的分段鎖稱爲Segment,它即相似於HashMap(JDK7和JDK8中HashMap的實現)的結構,即內部擁有一個Entry數組,數組中的每一個元素又是一個鏈表;同時又是一個ReentrantLock(Segment繼承了ReentrantLock)。
當須要put元素的時候,並非對整個hashmap進行加鎖,而是先經過hashcode來知道他要放在哪個分段中,而後對這個分段進行加鎖,因此當多線程put的時候,只要不是放在一個分段中,就實現了真正的並行的插入。
可是,在統計size的時候,可就是獲取hashmap全局信息的時候,就須要獲取全部的分段鎖才能統計。
分段鎖的設計目的是細化鎖的粒度,當操做不須要更新整個數組的時候,就僅僅針對數組中的一項進行加鎖操做。
##偏向鎖/輕量級鎖/重量級鎖
這三種鎖是指鎖的狀態,而且是針對Synchronized。在Java 5經過引入鎖升級的機制來實現高效Synchronized。這三種鎖的狀態是經過對象監視器在對象頭中的字段來代表的。
偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。下降獲取鎖的代價。<span style='color:red'>鎖一直只被一個線程持有,沒有發生鎖競爭</span>
輕量級鎖是指當鎖是偏向鎖的時候,被另外一個線程所訪問,偏向鎖就會升級爲輕量級鎖,其餘線程會經過自旋的形式嘗試獲取鎖,不會阻塞,提升性能。
重量級鎖是指當鎖爲輕量級鎖的時候,另外一個線程雖然是自旋,但自旋不會一直持續下去,當自旋必定次數的時候,尚未獲取到鎖,就會進入阻塞,該鎖膨脹爲重量級鎖。重量級鎖會讓他申請的線程進入阻塞,性能下降。
##自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會當即阻塞,而是採用循環的方式去嘗試獲取鎖,這樣的好處是減小線程上下文切換的消耗,缺點是循環會消耗CPU。
#AQS AbstractQueuedSynchronized 抽象隊列式的同步器,AQS定義了一套多線程訪問共享資源的同步器框架,許多同步類實現都依賴於它,如經常使用的ReentrantLock/Semaphore/CountDownLatch…
AQS維護了一個volatile int state(表明共享資源)和一個FIFO線程等待隊列(多線程爭用資源被阻塞時會進入此隊列)。
state的訪問方式有三種:
getState() setState() compareAndSetState()
AQS定義兩種資源共享方式:Exclusive(獨佔,只有一個線程能執行,如ReentrantLock)和Share(共享,多個線程可同時執行,如Semaphore/CountDownLatch)。
不一樣的自定義同步器爭用共享資源的方式也不一樣。自定義同步器在實現時只須要實現共享資源state的獲取與釋放方式便可,至於具體線程等待隊列的維護(如獲取資源失敗入隊/喚醒出隊等),AQS已經在頂層實現好了。自定義同步器實現時主要實現如下幾種方法:
isHeldExclusively():該線程是否正在獨佔資源。只有用到condition才須要去實現它。 tryAquire(int):獨佔方式。嘗試獲取資源,成功則返回true,失敗則返回false。 tryRelease(int):獨佔方式。嘗試釋放資源,成功則返回true,失敗則返回false。 tryAcquireShared(int):共享方式。嘗試獲取資源。負數表示失敗;0表示成功,但沒有剩餘可用資源;正數表示成功,且有剩餘資源。 tryReleaseShared(int):共享方式。嘗試釋放資源,若是釋放後容許喚醒後續等待結點返回true,不然返回false。
以ReentrantLock爲例,state初始化爲0,表示未鎖定狀態。A線程lock()時,會調用tryAcquire()獨佔該鎖並將state+1。此後,其餘線程再tryAcquire()時就會失敗,直到A線程unlock()到state=0(即釋放鎖)爲止,其餘線程纔有機會獲取該鎖。固然,釋放鎖以前,A線程本身是能夠重複獲取此鎖的(state會累加),這就是可重入的概念。但要注意,獲取多少次就要釋放多少次,這樣才能保證state是能回到零態的。
再以CountDownLatch爲例,任務分爲N個子線程去執行,state爲初始化爲N(注意N要與線程個數一致)。這N個子線程是並行執行的,每一個子線程執行完後countDown()一次,state會CAS減1。等到全部子線程都執行完後(即state=0),會unpark()主調用線程,而後主調用線程就會await()函數返回,繼續後餘動做。
通常來講,自定義同步器要麼是獨佔方法,要麼是共享方式,他們也只需實現tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一種便可。但AQS也支持自定義同步器同時實現獨佔和共享兩種方式,如ReentrantReadWriteLock。
#CAS