JAVA中主要鎖
synchronized
Reentrantlock
ReentrantReadWriteLock
問題引入
為什么需要鎖?
為什么JAVA有了synchronize還需要Reentrantlock和ReentrantReadWriteLock?
synchronize和lock分別怎么實現同步快(原子性,一致性,禁重排序)?
synchronize和lock分別怎么實現鎖的優化,可重入鎖,偏向鎖?
lock如何實現公平鎖(synchronize是非公平鎖)?
為什么需要鎖?
目的:
鎖的目的是防止資源的競爭,主要從 原子性(一致性),可見性,防止處理重排序 三個方面來處理, volatile滿足了后面兩個特性,JAVA從兩方面來實現鎖
為什么JAVA有了synchronize還需要Reentrantlock和ReentrantReadWriteLock?
synchronized與ReentrantLock ,使用上看區別
1, synchronize在獲取鎖阻塞的時候是不能打斷的
2, synchronize無超時機制,阻塞了的話只能一直阻塞造成死鎖
3,synchronize只能notify,wait,如果需要兩個或以上條件就不能用了,如: JAVA阻塞隊列的實現,需要用是否為空和是否已滿兩個條件來阻塞線程
看lock相關的API就知道, 主要就是解決這幾個問題
方法名稱 | 描述 |
lock | 獲取鎖,如果鎖無法獲取,那么當前的線程就變為不可被調度,直到鎖被獲取到 |
lockInterruptibly | 獲取鎖,除非當前線程被中斷。如果獲取到了鎖,那么立即返回,如果獲取不到,那么當前線程變得不可被調度,一直休眠直到下面兩件事情發生:1、當前線程獲取到了鎖
2、其他的線程中斷了當前的線程 |
tryLock | 如果調用的時候能夠獲取鎖,那么就獲取鎖並且返回true,如果當前的鎖無法獲取到,那么這個方法會立刻返回false |
tryLcok(long time,TimeUnit unit) | 在指定時間內嘗試獲取鎖如果可以獲取鎖,那么獲取鎖並且返回true,如果當前的鎖無法獲取,那么當前的線程變得不可被調度,直到下面三件事之一發生:1、當前線程獲取到了鎖
2、當前線程被其他線程中斷 3、指定的等待時間到了 |
unlock | 釋放當前線程占用的鎖 |
newCondition | 返回一個與當前的鎖關聯的條件變量。在使用這個條件變量之前,當前線程必須占用鎖。調用Condition的await方法,會在等待之前原子地釋放鎖,並在等待被喚醒后原子的獲取鎖 |
那ReentrantReadWriteLock呢?
讀寫鎖用於讀多寫少的情況,即當一條線程獲取寫鎖后,后面的讀鎖都被阻塞,等待獲取寫鎖的線程完成釋放。
場景,如本地緩存失效,當需要去DB拿數據進行寫入的操作,需要阻塞其它讀的操作.
當然,讀寫鎖也是可以基於notifyAll和wait實現
需要注意的是
- 如果無寫鎖,讀是不阻塞,
- 持有讀鎖后,不能直接調用寫鎖的lock方法 ,否則會造成死鎖
synchronize和lock分別怎么實現同步快(原子性,一致性,禁重排序)?
synchronized鎖
實現依賴
原子性,可見性和重排序都是依靠指令。方法同步和代碼塊同步依靠Monitor指令,代碼塊同步是使用monitorenter和monitorexit指令實現
鎖信息保存在JAVA對象頭里,准確說是Mark Word
synchronize的阻塞,依靠幾個隊列,屬於不公平鎖(線程先CAS競爭鎖,再進隊列)
ContentionList(LIFO)-->EntryList(LIFO)-->OnDeck-->Owner-->Wait Set (http://www.cnblogs.com/lykm02/p/4516777.html )
鎖的轉換方面
無鎖-->偏向鎖-->輕量鎖-->重量鎖 (http://blog.csdn.net/xad707348125/article/details/47189107)
synchronize和lock分別怎么實現鎖的優化,可重入鎖,偏向鎖?
偏向鎖和可重入鎖的實現
可重入鎖,即當本線程進入同一鎖時可以進行多次上鎖,當然也需要多次釋放
偏向鎖,即當獲取線程再次進入同步塊時不需要再次競爭(CAS),當某個Core CAS成功時必然會引起總線風暴,這就是所謂的本地延遲,本質上偏向鎖就是為了消除CAS,降低Cache一致性流量
原理:
1, 鎖保存當前鎖的線程,判斷同一個線程時允許
用一個計數器去記錄當前重入的次數,當進入時計數器+1, 當釋放鎖時計算器-1, 當為0時表示可競爭
synchronized
實現方式, 會在 Mark Word 存儲獲取鎖線程的ID,然后棧幀中也存儲線程ID,以后該線程再次進入同步塊(同步方法)時不需要花費CAS了。
lock
實現方式, 用JAVA代碼實現處理,跟蹤下 lock()(NonfairSyncCAS獲取鎖失敗)->acquire(AQS嘗試獲取鎖)-->tryAcquire(nonfairTryAcquire)-->nonfairTryAcquire(Sync 如下處理):
final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //再次判斷是否是未鎖狀態,state為0為未有線程獲取鎖 if (compareAndSetState(0, acquires)) { //CAS再次競爭獲取鎖,此處是公平鎖與非公平鎖的區別 setExclusiveOwnerThread(current); return true; } } //這里是是實現偏向鎖的關鍵,比較如果是當前鎖就不進入CLH隊列后面的競爭了 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
lock如何實現公平鎖(synchronize是非公平鎖)?
synchronize本身是非公平鎖,無公平性實現.
lock非公平鎖代碼(詳細如上)
if (compareAndSetState(0, acquires)) { //如鎖被釋放,是非公平鎖的話,用CAS再次競爭獲取鎖
公平鎖此段代碼如下:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {//如鎖被釋放,必須當隊列為空時才去CAS競爭鎖 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
lock如何實現公平鎖(synchronize是非公平鎖)?
synchronize本身是非公平鎖,無公平性實現.
lock非公平鎖代碼(詳細如上)
if (compareAndSetState(0, acquires)) { //如鎖被釋放,是非公平鎖的話,用CAS再次競爭獲取鎖
公平鎖此段代碼如下:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) {//如鎖被釋放,必須當隊列為空時才去CAS競爭鎖 setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
常見的鎖實現原理總結
自旋鎖
多個線程一起用CAS去嘗試獲取一個共同的可見性(volatile)的變量,獲取成功即為獲取鎖
如 所有線程都運行
/** * 自旋鎖方式去實現阻塞 * * 缺點:無法實現公平性,如果大量使用會增加CPU的Cache一致性流量開銷 */ public static void CASLock() { // 不斷去獲取CAS的鎖,如成功表示獲取鎖成功 while (state.compareAndSet(0, 1)) { } } public static void CASUnlock() { if (!state.compareAndSet(1, 0)) { // 釋放鎖異常 throw new RuntimeException(); } }
排隊自旋鎖 (Ticket Lock)
import java.util.concurrent.atomic.AtomicInteger; public class TicketLock { private AtomicInteger serviceNum = new AtomicInteger(); // 服務號 private AtomicInteger ticketNum = new AtomicInteger(); // 排隊號 public int lock() { // 首先原子性地獲得一個排隊號 int myTicketNum = ticketNum.getAndIncrement(); // 只要當前服務號不是自己的就不斷輪詢 while (serviceNum.get() != myTicketNum) { } return myTicketNum; } public void unlock(int myTicket) { // 只有當前線程擁有者才能釋放鎖 int next = myTicket + 1; serviceNum.compareAndSet(myTicket, next); } }
CLH鎖
CLH是在前驅節點的屬性上自旋,
組成一個隊列后,每個節點都有保存當前節點獲取鎖的狀態,和前一個節點的指向,獲取鎖的步驟
1, 新加個節點,並把節點通過自旋指向tail節點
2, 成功后,不停判斷指向節點的鎖狀態,當前節點鎖釋放時獲取鎖
3, 釋放鎖,改變自身的鎖持有狀態就行
MCS鎖
而MCS是在本地屬性變量上自旋。
歡迎關注我的公眾號, 一起來構建我們的知識體系