基礎篇系列,JAVA的並發包 - 鎖


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實現

需要注意的是

  1. 如果無寫鎖,讀是不阻塞,
  2. 持有讀鎖后,不能直接調用寫鎖的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是在本地屬性變量上自旋。

 

 

歡迎關注我的公眾號, 一起來構建我們的知識體系

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM