ReentrantLock、ReentrantReadWriteLock

一、ReentrantLock

ReentrantLock是可重入鎖,已經獲得鎖的線程可以再次獲得鎖,如果一個線程獲取了n個鎖,那麼釋放的時候同樣要釋放n個鎖。

同步組件主要是通過重寫AQS的幾個protected方法來表達自己的同步語義。ReentrantLock主要的方法:

   //獲取鎖
    public void lock() {
        sync.lock();
    }
   //釋放鎖
    public void unlock() {
        sync.release(1);
    }

可以看到都調用了syn的方法,syn是 ReentrantLock的抽象內部類,繼承了AbstractQueuedSynchronizer。ReentrantLock裏面大部分的功能都是委託給Sync來實現的,同時Sync內部定義了lock()抽象方法由其子類去實現。

unlock調用了AQS的release方法。FairSync(公平鎖) 和NonfairSync(非公平鎖)繼承了Syn。

1、公平鎖

(1)獲取鎖

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);//獲取1個鎖。該方法調用父類的,而父類又會調用子類覆寫的tryAcquire方法
        }

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//1.獲取當前線程
            int c = getState();//2.獲取同步狀態
            if (c == 0) {//3.1如果狀態爲0,表示沒有獲取過鎖
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);//設置獲得獨佔鎖的線程爲當前線程
                    return true;
                }
            }
            
            //線程重入
            else if (current == getExclusiveOwnerThread()) {//3.2狀態大於0,表明鎖被佔用了,如果持有的線程是當前線程,直接加上獲取鎖的數目
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }
/**在同步隊列中是否有前驅節點的判斷,如果有前驅節點說明有線程比當前線程更早的請求資源,根據公平性,當前線程請求資源失敗*/  
  public final boolean hasQueuedPredecessors() {
        Node t = tail;
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

(2)釋放鎖

參考3

2、非公平鎖

(1)獲取鎖

    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        final void lock() {
            if (compareAndSetState(0, 1))//1.如果鎖是空閒的,直接獲取
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);//2.否則嘗試獲取鎖,AQS會調用tryAcquire獲取
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);//調用Syn的nonfairTryAcquire方法
        }
    }
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {//1.如果同步狀態爲0,直接獲取鎖
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {//2.線程重入
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 非公平鎖和公平鎖的不同之處在於公平鎖會檢查在同步隊列中是否有線程比當前線程更早的請求資源,如果有就不允許獲取鎖,而非公平鎖則不會檢查,直接獲取。公平鎖每次都是從同步隊列中的第一個節點獲取到鎖,而非公平性鎖則不一定,有可能剛釋放鎖的線程能再次獲取到鎖。

(2)釋放鎖

參考3

3、釋放鎖

釋放鎖調用的是Syn的tryRelease方法

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;//1.減去釋放的數量(reentranlock默認是減1)
            if (Thread.currentThread() != getExclusiveOwnerThread())//如果當前線程不是持有鎖的線程,拋出異常
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {//2.如果同步狀態此時爲0,說明已經被釋放了
                free = true;
                setExclusiveOwnerThread(null);//重置持有鎖線程記錄
            }
            setState(c);//3.更新同步狀態
            return free;
        }

4、總結:

(摘自JAVA併發十:徹底理解ReentrantLock 作者:Java菜鳥奮鬥史)

(1)公平鎖每次獲取到鎖爲同步隊列中的第一個節點,保證請求資源時間上的絕對順序,而非公平鎖有可能剛釋放鎖的線程下次繼續獲取該鎖,則有可能導致其他線程永遠無法獲取到鎖,造成“飢餓”現象。

(2)公平鎖爲了保證時間上的絕對順序,需要頻繁的上下文切換,而非公平鎖會降低一定的上下文切換,降低性能開銷。因此,ReentrantLock默認選擇的是非公平鎖,則是爲了減少一部分上下文切換,保證了系統更大的吞吐量。

(摘自《Java併發編程實戰)

(3)即使對於公平鎖,可輪詢的trylock仍然會“插隊”

(4)當持有鎖的時間相對較長,或者請求鎖的平均時間間隔較長,那麼應該使用公平鎖。在這些情況下,“插隊”帶來的吞吐量提升(當鎖處於可用狀態時,線程卻還處於被喚醒的過程中)則可能不會出現

5、選擇ReentrantLock還是synchronized?

(1)ReentrantLock的危險性比同步機制要高(如果忘記在finally塊調用unlock)

(2)內置鎖可以使用Thread Dump來監測死鎖線程,便於調試,但JVM無法識別哪些線程持有ReentrantLock

(3)當需要一些高級功能時才應該使用ReentrantLock,比如:可定時的、可輪詢的與可中斷的鎖獲取操作,公平隊列,以及非塊結構的鎖,否則,優先使用synchronized。

注意:兩種機制如果混合使用,容易出錯以及令人困惑

 二、ReentrantReadWriteLock

ReentrantLock是獨佔鎖,同一時刻只有一個線程能夠獲取,但在一些場景中大部分只是讀數據,寫數據很少,如果僅僅是讀數據的話並不會影響數據正確性(出現髒讀),讀寫鎖允許同一時刻被多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他的寫線程都會被阻塞

先解析一下exclusiveCount方法,該方法返回鎖被一個線程重複獲取的次數。在ReentrantLock中使用一個int類型的state來表示同步狀態,該值表示鎖被一個線程重複獲取的次數。ReentrantReadWriteLock中也是用int類型的state來表示同步狀態,但是讀寫鎖需要分別表示讀狀態和寫狀態,這個怎麼辦呢?

ReentrantReadWriteLock通過將state劃分爲高16位和低16位來分別維護讀寫狀態,其中高16位維護的是低16位維護的是。如下圖所示(摘自深入理解讀寫鎖—ReadWriteLock源碼分析 作者: xingfeng_coder

以寫鎖爲例,可以看到EXCLUSIVE_MASK 是(1 << SHARED_SHIFT) - 1,也就是1左移16位-1,這時EXCLUSIVE_MASK=ox0000FFFF,而狀態c&EXCLUSIVE_MASK,也就是保留了低16位,抹去高16位。

static final int SHARED_SHIFT   = 16;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

再看看讀鎖是怎樣的? 讀鎖直接通過無符號右移16位,獲得了高16位

static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

1、寫鎖

(1)獲取鎖

        protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3. Otherwise, this thread is eligible for lock if
             *    it is either a reentrant acquire or
             *    queue policy allows it. If so, update state
             *    and set owner.
             */
            Thread current = Thread.currentThread();//1.獲取當前線程
            int c = getState();//2.獲取同步狀態
            int w = exclusiveCount(c);//3.獲取寫鎖獲取次數
            if (c != 0) {//4.1如果同步狀態不爲0,說明有線程已經獲取更新了同步狀態
                // (Note: 如果 c != 0 and w == 0 then shared count != 0,讀鎖已被讀線程獲取)
                if (w == 0 || current != getExclusiveOwnerThread())//當讀鎖已被讀線程獲取或者當前線程不是已經獲取寫鎖的線程,獲取失敗
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)//超出了最大範圍
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);//獲取成功,更新狀態
                return true;
            }    
            //4.2 同步狀態爲0,
            //在嘗試獲取同步狀態之前先調用writerShouldBlock()是根據公平鎖還是非公平鎖來判斷是否應該直接阻塞(也就是是否能夠直接獲取)
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))//設置狀態
                return false;
            setExclusiveOwnerThread(current);//獲取成功,設置獲取鎖的線程爲當前線程
            return true;
        }
 static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;

        final boolean writerShouldBlock() {
            return false; //直接返回false
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive(); //檢查持鎖線程的後繼結點是否爲寫鎖
        }
    }

    /**
     * Fair version of Sync
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();//檢查是否有前繼結點
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();//檢查是否有前繼結點
        }
    }
    /**
     * 在非公平鎖的實現中, 只要同步狀態隊列中有寫線程正在等待的話, 就應該阻塞讀線程, 不讓其獲取同步狀態. 這樣做是爲了防止寫線程出現飢餓現象.
     */
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null && //頭結點是否爲空
            (s = h.next)  != null && //後繼結點是否爲空
            !s.isShared()         && //後繼是否是共享模式(讀鎖)
            s.thread != null;    //後繼結點中的線程是否爲空
    }

(2)釋放鎖

        protected final boolean tryRelease(int releases) {
            if (!isHeldExclusively())//1、判斷是否是當前線程佔有鎖
                throw new IllegalMonitorStateException();
            int nextc = getState() - releases;//2、減去釋放的數量
            //3、當前寫狀態是否爲0,爲0則釋放寫鎖
            boolean free = exclusiveCount(nextc) == 0;
            if (free)
                setExclusiveOwnerThread(null);
            //4、更新狀態
            setState(nextc);
            return free;
        }

2、讀鎖

(1)HoldCounter、ThreadLocalHoldCounter

HoldCounter可以理解爲是綁定線程上的一個計數器,而ThradLocalHoldCounter則是線程綁定的ThreadLocal。

        /**
         * 每個線程的讀鎖計數器
         * 由ThreadLocal(ThreadLocalHoldCounter )維護;緩存在cachedHoldCounter
         */
         static final class HoldCounter {
            int count = 0;//獲取讀鎖數量
            // Use id, not reference, to avoid garbage retention
            final long tid = getThreadId(Thread.currentThread());//獲取鎖的線程id
         }
        /**
         * 保存上一個成功獲取讀鎖的線程的HoldCounter 
         */
         private transient HoldCounter cachedHoldCounter;
         
        /** ThreadLocal子類,initialValue方法返回一個HoldCounter 實例 */
         static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
            public HoldCounter initialValue() {
                return new HoldCounter();
            }
         }

        /**
         * 當前線程擁有的讀鎖數量。當讀鎖數量減至0時刪除
         */
         private transient ThreadLocalHoldCounter readHolds;

(2)獲取鎖

         protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)//1.如果寫鎖不爲0,且持有寫鎖的不是當前線程,說明其他線程持有寫鎖,不可獲取讀鎖,返回-1
                return -1;
            int r = sharedCount(c);//獲取讀鎖獲取次數
           //2.1如果成功獲取了讀鎖
             if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                if (r == 0) {//2.1.1 如果讀鎖未被持有
                    firstReader = current;//更新第一個獲取讀鎖的線程
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {//2.1.2 如果當前線程已經獲取讀鎖
                    firstReaderHoldCount++;//持有鎖數加1
                } else {//2.1.3 其他線程擁有鎖
                    HoldCounter rh = cachedHoldCounter;//更新最近成功獲取讀鎖的HoldCounter 
                    if (rh == null || rh.tid != getThreadId(current))//如果rh爲空或rh內線程id不等於當前線程id
                        cachedHoldCounter = rh = readHolds.get();//更新緩存、rh
                    else if (rh.count == 0)//如果已獲取讀鎖數量爲0
                        readHolds.set(rh);//更新爲當前線程
                    rh.count++;
                }
                return 1;
            }
            //2.2沒有成功獲取讀鎖
            return fullTryAcquireShared(current);
        }
        /**
         * 獲取讀鎖完整版本。處理CAS操作失敗以及處理tryAcquireShared沒有實現的可重入
         */
        final int fullTryAcquireShared(Thread current) {

            HoldCounter rh = null;
            for (;;) {
                int c = getState();
                if (exclusiveCount(c) != 0) {
                    if (getExclusiveOwnerThread() != current)//其他線程持有寫鎖
                        return -1;
                    // else we hold the exclusive lock; blocking here
                    // would cause deadlock.
                } else if (readerShouldBlock()) {
                    // Make sure we're not acquiring read lock reentrantly
                    if (firstReader == current) {//如果當前線程是第一個獲取讀鎖的線程
                        // assert firstReaderHoldCount > 0;
                    } else {
                        if (rh == null) {
                            rh = cachedHoldCounter;
                            if (rh == null || rh.tid != getThreadId(current)) {
                                rh = readHolds.get();
                                if (rh.count == 0)
                                    readHolds.remove();
                            }
                        }
                        if (rh.count == 0)
                            return -1;
                    }
                }
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                if (compareAndSetState(c, c + SHARED_UNIT)) {//CAS更改狀態,讀鎖加1
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) { //如果獲取讀鎖的線程爲第一次獲取讀鎖的線程,則firstReaderHoldCount重入數 + 1
                        firstReaderHoldCount++;
                    } else {
                        if (rh == null)
                            rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                        cachedHoldCounter = rh; // cache for release
                    }
                    return 1;
                }
            }
        }

(3)釋放鎖

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();//獲取當前線程
            
            //1.更新HoldCounter 和 ThreadLocalHoldCounter
            if (firstReader == current) {//1.1如果想要釋放鎖的線程爲第一個獲取鎖的線程,直接跳到第2步
                // assert firstReaderHoldCount > 0;
                if (firstReaderHoldCount == 1)
                    firstReader = null;
                else
                    firstReaderHoldCount--;
            } else {//1.2否則,釋放cachedHoldCounter存儲線程獲取的鎖
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();//獲取上一個獲取讀鎖的線程的HoldCounter 
                int count = rh.count;
                if (count <= 1) {
                    readHolds.remove();
                    if (count <= 0)
                        throw unmatchedUnlockException();
                }
                --rh.count;
            }
            //2.CAS更新狀態
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // Releasing the read lock has no effect on readers,
                    // but it may allow waiting writers to proceed if
                    // both read and write locks are now free.
                    return nextc == 0;
            }
        }

3、鎖降級

鎖降級是寫鎖降級爲讀鎖,持有寫鎖的情況下獲取讀鎖。

如果當前線程擁有寫鎖,然後將其釋放,最後再獲取讀鎖,這種分段完成的過程不能稱之爲鎖降級。

以下是來自源碼的一個demo:

從寫鎖降級成讀鎖,並不會自動釋放當前線程獲取的寫鎖,仍然需要顯式的釋放,否則別的線程永遠也獲取不到寫鎖。

public class CachedData {

	Object data;
	volatile boolean cacheValid;
	final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

	void processCachedData() {
		   rwl.readLock().lock();
		   if (!cacheValid) {
		     // Must release read lock before acquiring write lock
		     rwl.readLock().unlock();//必須先釋放讀鎖
		     rwl.writeLock().lock();//獲取寫鎖
		     try {
		       // 獲取寫鎖後需要重新檢查狀態,因爲其他線程可能在我們檢查完狀態還沒獲取鎖前已經獲取寫鎖並修改狀態了
		       if (!cacheValid) {
		         data = "Datahahaha";
		         cacheValid = true;
		       }
		       // Downgrade by acquiring read lock before releasing write lock
		       rwl.readLock().lock();//鎖降級。之所以要先獲取讀鎖再釋放寫鎖是因爲如果先釋放寫鎖,有可能因爲其他線程獲取了寫鎖導致讀鎖無法獲取
		     } finally {
		       rwl.writeLock().unlock(); // 釋放寫鎖,此時仍然持有讀鎖
		     }
		   }
		
		   try {
		     use(data);
		   } finally {
		     rwl.readLock().unlock();
		   }
		 }

	private void use(Object data2) {
		
	}

}

4、總結

讀寫鎖只能降級,不能升級(同一個線程中,在沒有釋放讀鎖的情況下,就去申請寫鎖),很容易造成死鎖(如果兩個讀線程試圖同時升級爲寫入鎖,那麼二者都不會釋放讀鎖)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章