欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談一下Java中的幾種JVM級別的鎖

 更新時間:2023年08月04日 10:54:49   作者:趕路人兒  
這篇文章主要介紹了淺談一下Java中的幾種JVM級別的鎖,當存在安全漏洞時,也必須有相應的防護措施。順應這種趨勢,虛擬"鎖"被發(fā)明出來,以解決線程的安全問題。在這篇文章中,我們將研究多年來出現(xiàn)的?Java?中幾種典型的?JVM?級鎖,需要的朋友可以參考下

前言

根據摩爾定律,計算機的性能將繼續(xù)飆升,因為計算基礎設施的相關成本將隨著時間的推移繼續(xù)下降。

具體到CPU,已經從簡單的單核系統(tǒng)發(fā)展到多核系統(tǒng),緩存性能也有了飛躍性的提升。隨著多核 CPU 的出現(xiàn),計算機現(xiàn)在可以同時運行多個任務。

并且,隨著硬件開發(fā)的多項提升帶來的顯著效率提升,軟件層面的多線程編程已經成為必然趨勢。

然而,多線程編程也帶來了一些數(shù)據安全問題。

隨著所有這些趨勢的發(fā)展,業(yè)界已經認識到,當存在安全漏洞時,也必須有相應的防護措施。

順應這種趨勢,虛擬“鎖”被發(fā)明出來,以解決線程的安全問題。

在這篇文章中,我們將研究多年來出現(xiàn)的 Java 中幾種典型的 JVM 級鎖。

1、synchronized

synchronized 關鍵字是 Java 中經典且非常典型的鎖。

事實上,它也是最常用的一種。在 JDK 1.6 之前,“synchronized”是一個相當“重量級”的鎖。

不過隨著Java的更新升級,這個鎖也在不斷的優(yōu)化。

如今,這把鎖變得不那么“沉重”了。而且,事實上,在某些場景下,它的性能甚至優(yōu)于典型的輕量級鎖。

而且在帶有synchronized關鍵字的方法和代碼塊中,同一時刻只允許一個線程訪問特定的代碼段,防止多個線程并發(fā)修改同一塊數(shù)據。

1.1)鎖升級

在JDK 1.5(含)之前,synchronized關鍵字的底層實現(xiàn)比較重,因此被稱為“重量級鎖”。

不過在JDK 1.5之后,對“synchronized”鎖進行了各種改進,變得不那么重量級了。

因此,實現(xiàn)方法就是鎖升級過程。我們先看看JDK 1.5之后“同步”鎖是怎么實現(xiàn)的。

說到同步鎖的原理,首先要了解Java對象在內存中的布局。

如上圖所示,創(chuàng)建一個對象后,該對象在JVM HotSpot虛擬機的Java內存中的存儲布局可以分為三種。

1)對象頭

該區(qū)域存儲的信息分為兩部分。

對象本身的運行時數(shù)據(MarkWord):該數(shù)據存儲hashCode、垃圾收集(GC)世代年齡、鎖類型標志、偏向鎖線程的ID、指向LockRecord的Compare and Swap(CAS)鎖指針的線程,除了其他信息。 synchronized鎖的機制與這部分(MarkWord)高度相關。 MarkWord 的最低三位表示鎖定狀態(tài)。對于這三個位,其中一個是偏向鎖定位,另外兩個是普通鎖定位。對象的類指針(Class Pointer):對象的指針是指向其類元數(shù)據的指針。 JVM 使用它來確定實例的類。

 2)實例數(shù)據區(qū)

該區(qū)域存放對象的有效信息,如對象中所有字段的內容。

3)對齊填充

JVM HotSpot的實現(xiàn)規(guī)定對象的起始地址必須是8字節(jié)的整數(shù)倍。也就是說,64位操作系統(tǒng)一次讀取的數(shù)據是64位的整數(shù)倍,即8個字節(jié)。因此,HotSpot 進行“對齊”以高效地讀取對象。如果對象的實際內存大小不是 8 字節(jié)的整數(shù)倍,HotSpot 會將對象“填充”為 8 字節(jié)的整數(shù)倍。因此,對齊和填充區(qū)域的大小是動態(tài)的。

1.2)synchronized鎖升級

當線程進入synchronized狀態(tài)并嘗試獲取鎖時,synchronized鎖的升級過程如下。

 總結一下,同步鎖的升級順序是:偏向鎖>輕量級鎖>重量級鎖。

The detailed triggering of lock upgrade in each step is as follows.

1)Biased Lock:(偏向鎖)

在 JDK 1.8 中,默認是輕量級鎖。但是,通過設置 -XX:BiasedLockingStartupDelay = 0,在同步對象后立即附加偏向鎖。當線程處于偏向鎖狀態(tài)時,MarkWord記錄當前線程的ID。

2)Upgrading to the Lightweight Lock:(升級到輕量級鎖)  當下一個線程競爭偏向鎖時,系統(tǒng)首先檢查MarkWord中存儲的線程ID是否與本線程的ID一致。如果不是,系統(tǒng)立即撤銷偏向鎖,升級為輕量級鎖。每個線程在自己的線程棧中生成一個LockRecord(LR)。然后,每個線程通過CAS操作(自旋)將鎖對象頭中的MarkWord設置為指向自己LR的指針。如果一個線程成功設置了MarkWord,那么該線程就獲得了鎖。因此,為“同步”執(zhí)行的 CAS 操作是通過 HotSpot 的 bytecodeInterpreter.cpp 文件中的 C++ 代碼完成的。

3)Upgrading to the Heavyweight Lock:(升級到重量級鎖)

如果鎖競爭加劇,比如線程自旋數(shù)或者自旋線程數(shù)超過了一個閾值,這個閾值是JVM自己控制的,對于1.6以后的JDK版本,鎖升級為重量級鎖。然后,重量級鎖向操作系統(tǒng)申請資源。

同時,該線程也被掛起,進入操作系統(tǒng)內核態(tài)的等待隊列,等待操作系統(tǒng)對其進行調度,映射回用戶態(tài)。在重量級鎖中,需要從內核態(tài)轉換到用戶態(tài),這個過程需要比較長的時間,這也是它被定性為“重量級”的原因之一。

1.3)其他:

1)可重入性:

synchronized鎖內部有一個強制原子性的鎖機制,就是可重入鎖。當一個線程使用synchronized方法時,會調用該對象的另一個synchronized方法。即線程獲得對象鎖后,一旦再次請求該對象鎖,該線程總能獲得該鎖。在Java中,線程獲取對象鎖的操作是基于線程的,而不是基于調用的。

synchrnoized塊、語句中所對應的對象頭中的MarkWord記錄了鎖的線程持有者和計數(shù)器。當一個線程請求成功后,JVM記錄持有鎖的線程,并將計數(shù)置為1。此時,如果有另一個線程請求鎖,則該線程必須等待。

當持有鎖的線程再次請求鎖時,可以再次獲得鎖,計數(shù)隨之遞增。當線程退出同步方法或塊時,計數(shù)會遞減。最后,如果計數(shù)為 0,則釋放鎖。

2)Pessimistic Lock (Mutex and Exclusive Lock):(悲觀鎖(互斥和排他鎖))

synchronized鎖是悲觀鎖,更確切地說是排他鎖。換句話說,如果當前線程獲得了鎖,任何其他需要鎖的線程都必須等待。鎖競爭繼續(xù),直到持有鎖的線程釋放鎖。

2、ReentrantLock

ReentrantLock 和synchronized類似,但其實現(xiàn)方式與同步鎖有較大區(qū)別。具體來說,它是基于經典的AbstractQueueSyncronized(AQS)實現(xiàn)的。 AQS是基于volatile和CAS實現(xiàn)的。 AQS維護volatile類型的state變量來統(tǒng)計重入鎖的重入嘗試次數(shù)。同樣,加鎖和釋放鎖也是基于這個變量。 ReentrantLock提供了一些synchronized鎖所沒有的額外特性,所以比synchronized鎖要好。

2.1)ReentrantLock特性: 

1)可重入:

ReentrantLock 和 synchronized 關鍵字一樣,都是支持重入的鎖。但是,它們的實現(xiàn)方法略有不同。 ReentrantLock通過AQS的狀態(tài)來判斷資源是否已經被鎖定。對于同一個線程,如果被鎖定,狀態(tài)值加1,如果被解鎖,狀態(tài)值減1。注意解鎖只對當前獨占線程有效,否則會出現(xiàn)異常。如果狀態(tài)值為0,則解鎖成功。

2)手動加鎖、解鎖:

synchronized 關鍵字自動鎖定和解鎖。相比之下,ReentrantLock 需要 lock() 和 unlock() 方法以及 try/finally 語句塊來手動鎖定和解鎖。

3)lock timeout:

synchronized 關鍵字不能設置鎖定超時時間。如果在獲取鎖的線程中發(fā)生死鎖,其他線程將保持阻塞狀態(tài)。 ReentrantLock 提供了 tryLock 方法,可以為獲得鎖的線程設置超時時間。如果超過超時時間,則跳過該線程,不執(zhí)行任何操作,從而防止死鎖。

4)Fair and Unfair Locks

synchronized 關鍵字是一種非公平鎖,第一個搶到鎖的線程先運行。通過在ReentrantLock的構造方法中設置true或false,可以實現(xiàn)公平鎖和非公平鎖。如果設置為true,線程需要遵循“先到先得”的規(guī)則。每次線程要獲取鎖時,都會構造一個線程節(jié)點,然后追加到雙向鏈表的“尾部”進行排隊,等待隊列中的前一個節(jié)點釋放鎖資源。

5)可中斷:

ReentrantLock 中的 lockInterruptibly() 方法允許線程在被阻塞時響應中斷。比如線程t1通過lockInterruptibly()方法獲得了一個ReentrantLock,運行了一個long task。其他線程可以使用interrupt()方法立即中斷線程t1的運行,獲得線程t1的ReentrantLock。但是,ReentrantLock的lock()方法或者有synchronized鎖的線程是不會響應其他線程的interrupt()方法的,直到這個方法主動釋放鎖。

2.2)ReentrantReadWriteLock

ReentrantReadWriteLock(讀寫鎖)其實就是兩把鎖,一把是WriteLock(寫鎖),一把是ReadLock(讀鎖)。讀寫鎖的規(guī)則是:read-read non-exclusive, read-write exclusive, and write-write exclusive。在一些實際場景中,讀的頻率遠高于寫的頻率。如果使用普通鎖進行并發(fā)控制,讀寫互斥,效率低下。

為了優(yōu)化這種場景下的運行效率,讀寫鎖應運而生??偟膩碚f,獨占鎖的低效率來源于高并發(fā)下臨界區(qū)的激烈競爭,導致線程上下文切換。當并發(fā)不是很高的時候,讀寫鎖的效率可能會低于排他鎖,因為需要額外維護。因此,您需要根據實際情況選擇合適的鎖具。

ReentrantReadWriteLock也是基于AQS實現(xiàn)的。 ReentrantLock 和 ReentrantReadWriteLock 的區(qū)別在于后者具有共享鎖和排它鎖的屬性。讀寫鎖中的加鎖和解鎖是基于Sync的,繼承自AQS。主要由AQS中的state和node中的waitState變量實現(xiàn)。

讀寫鎖的實現(xiàn)與普通互斥鎖的主要區(qū)別在于需要分別記錄讀鎖狀態(tài)和寫鎖狀態(tài),等待隊列需要區(qū)別對待這兩種鎖操作。在ReentrantReadWriteLock中,AQS中的int型狀態(tài)分為高16位和低16位分別記錄讀鎖和寫鎖狀態(tài),如下圖。

 1)The WriteLock (Write Lock) Is a Pessimistic Lock (Exclusive Lock or Mutex)

通過計算state&((1<<16)-1),將state的高16位全部擦除。因此,狀態(tài)的低位記錄了寫鎖的重入次數(shù)。

下面是獲取寫鎖的源碼。

/**
         * 獲取寫鎖
           Acquires the write lock.
         *  如果此時沒有任何線程持有寫鎖或者讀鎖,那么當前線程執(zhí)行CAS操作更新status,
         *  若更新成功,則設置讀鎖重入次數(shù)為1,并立即返回
         * <p>Acquires the write lock if neither the read nor write lock
         * are held by another thread
         * and returns immediately, setting the write lock hold count to
         * one.
         *  如果當前線程已經持有該寫鎖,那么將寫鎖持有次數(shù)設置為1,并立即返回
         * <p>If the current thread already holds the write lock then the
         * hold count is incremented by one and the method returns
         * immediately.
         *  如果該鎖已經被另外一個線程持有,那么停止該線程的CPU調度并進入休眠狀態(tài),
         *  直到該寫鎖被釋放,且成功將寫鎖持有次數(shù)設置為1才表示獲取寫鎖成功
         * <p>If the lock is held by another thread then the current
         * thread becomes disabled for thread scheduling purposes and
         * lies dormant until the write lock has been acquired, at which
         * time the write lock hold count is set to one.
         */
        public void lock() {
            sync.acquire(1);
        }
/**
     * 該方法為以獨占模式獲取鎖,忽略中斷
     * 如果調用一次該"tryAcquire"方法更新status成功,則直接返回,代表搶鎖成功
     * 否則,將會進入同步隊列等待,不斷執(zhí)行"tryAcquire"方法嘗試CAS更新status狀態(tài),直到成功搶到鎖
     * 其中"tryAcquire"方法在NonfairSync(公平鎖)中和FairSync(非公平鎖)中都有各自的實現(xiàn)
     *
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    protected final boolean tryAcquire(int acquires) {
            /*
             * Walkthrough:
             * 1、如果讀寫鎖的計數(shù)不為0,且持有鎖的線程不是當前線程,則返回false
             * 1. If read count nonzero or write count nonzero
             *    and owner is a different thread, fail.
             * 2、如果持有鎖的計數(shù)不為0且計數(shù)總數(shù)超過限定的最大值,也返回false
             * 2. If count would saturate, fail. (This can only
             *    happen if count is already nonzero.)
             * 3、如果該鎖是可重入或該線程在隊列中的策略是允許它嘗試搶鎖,那么該線程就能獲取鎖
             * 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();
            //獲取讀寫鎖的狀態(tài)
            int c = getState();
            //獲取該寫鎖重入的次數(shù)
            int w = exclusiveCount(c);
            //如果讀寫鎖狀態(tài)不為0,說明已經有其他線程獲取了讀鎖或寫鎖
            if (c != 0) {
                //如果寫鎖重入次數(shù)為0,說明有線程獲取到讀鎖,根據"讀寫鎖互斥"原則,返回false
                //或者如果寫鎖重入次數(shù)不為0,且獲取寫鎖的線程不是當前線程,根據"寫鎖獨占"原則,返回false
                // (Note: if c != 0 and w == 0 then shared count != 0)
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
               //如果寫鎖可重入次數(shù)超過最大次數(shù)(65535),則拋異常
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //到這里說明該線程是重入寫鎖,更新重入寫鎖的計數(shù)(+1),返回true
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            //如果讀寫鎖狀態(tài)為0,說明讀鎖和寫鎖都沒有被獲取,會走下面兩個分支:
            //如果要阻塞或者執(zhí)行CAS操作更新讀寫鎖的狀態(tài)失敗,則返回false
            //如果不需要阻塞且CAS操作成功,則當前線程成功拿到鎖,設置鎖的owner為當前線程,返回true
            if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

Source code for releasing the write lock:

/*
  * Note that tryRelease and tryAcquire can be called by
  * Conditions. So it is possible that their arguments contain
  * both read and write holds that are all released during a
  * condition wait and re-established in tryAcquire.
  */
 protected final boolean tryRelease(int releases) {
     //若鎖的持有者不是當前線程,拋出異常
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     //寫鎖的可重入計數(shù)減掉releases個
     int nextc = getState() - releases;
     //如果寫鎖重入計數(shù)為0了,則說明寫鎖被釋放了
     boolean free = exclusiveCount(nextc) == 0;
     if (free)
        //若寫鎖被釋放,則將鎖的持有者設置為null,進行GC
        setExclusiveOwnerThread(null);
     //更新寫鎖的重入計數(shù)
     setState(nextc);
     return free;
 }

2)The ReadLock (Read Lock) Is a Shared Lock (Optimistic Lock)

通過計算無符號補零的狀態(tài)>>>16,引入了 16 個附加位。所以state的高位記錄了寫鎖的重入次數(shù)。

獲取讀鎖的過程比獲取寫鎖稍微復雜一些。首先系統(tǒng)判斷寫鎖的計數(shù)是否為0,當前線程沒有持有排他鎖。如果是,系統(tǒng)直接返回結果。如果不是,系統(tǒng)檢查讀線程是否需要阻塞,讀鎖數(shù)量是否小于閾值,設置狀態(tài)比較是否成功。

如果當前不存在讀取鎖,則設置第一個讀取線程的 firstReader 和 firstReaderHoldCount。如果當前線程是第一個讀取線程,則 firstReaderHoldCount 值遞增。否則,設置當前線程對應的HoldCounter對象的值。更新成功后,當前線程的重入計數(shù)記錄在當前線程副本中的firstReaderHoldCount的readHolds(ThreadLocal類型)中。這是為了實現(xiàn)JDK 1.6中新增的getReadHoldCount()方法。該方法可以獲得當前線程重新進入共享鎖的次數(shù)。即狀態(tài)中記錄了多個線程的總重入次數(shù)。

引入這個方法讓代碼復雜了很多,但是原理還是很簡單的:如果只有一個線程,則不需要使用ThreadLocal,直接將重入計數(shù)存儲在firstReaderHoldCount成員變量中即可。當另一個線程發(fā)生時,需要使用ThreadLocal變量,readHolds。每個線程都有自己的副本,用來保存自己的重入計數(shù)。

下面是獲取讀鎖的源碼:

/**
         * 獲取讀鎖
         * Acquires the read lock.
         * 如果寫鎖未被其他線程持有,執(zhí)行CAS操作更新status值,獲取讀鎖后立即返回
         * <p>Acquires the read lock if the write lock is not held by
         * another thread and returns immediately.
         * 
         * 如果寫鎖被其他線程持有,那么停止該線程的CPU調度并進入休眠狀態(tài),直到該讀鎖被釋放
         * <p>If the write lock is held by another thread then
         * the current thread becomes disabled for thread scheduling
         * purposes and lies dormant until the read lock has been acquired.
         */
        public void lock() {
            sync.acquireShared(1);
        }
   /**
     * 該方法為以共享模式獲取讀鎖,忽略中斷
     * 如果調用一次該"tryAcquireShared"方法更新status成功,則直接返回,代表搶鎖成功
     * 否則,將會進入同步隊列等待,不斷執(zhí)行"tryAcquireShared"方法嘗試CAS更新status狀態(tài),直到成功搶到鎖
     * 其中"tryAcquireShared"方法在NonfairSync(公平鎖)中和FairSync(非公平鎖)中都有各自的實現(xiàn)
     * (看這注釋是不是和寫鎖很對稱)
     * Acquires in shared mode, ignoring interrupts.  Implemented by
     * first invoking at least once {@link #tryAcquireShared},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquireShared} until success.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquireShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
    protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1、如果已經有其他線程獲取到了寫鎖,根據"讀寫互斥"原則,搶鎖失敗,返回-1
             * 1.If write lock held by another thread, fail.
             * 2、如果該線程本身持有寫鎖,那么看一下是否要readerShouldBlock,如果不需要阻塞,
             *    則執(zhí)行CAS操作更新state和重入計數(shù)。
             *    這里要注意的是,上面的步驟不檢查是否可重入(因為讀鎖屬于共享鎖,天生支持可重入)
             * 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、如果因為CAS更新status失敗或者重入計數(shù)超過最大值導致步驟2執(zhí)行失敗
             *    那就進入到fullTryAcquireShared方法進行死循環(huán),直到搶鎖成功
             * 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();
            //獲取該讀寫鎖狀態(tài)
            int c = getState();
            //如果有線程獲取到了寫鎖 ,且獲取寫鎖的不是當前線程則返回失敗
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            //獲取讀鎖的重入計數(shù)
            int r = sharedCount(c);
            //如果讀線程不應該被阻塞,且重入計數(shù)小于最大值,且CAS執(zhí)行讀鎖重入計數(shù)+1成功,則執(zhí)行線程重入的計數(shù)加1操作,返回成功
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                //如果還未有線程獲取到讀鎖,則將firstReader設置為當前線程,firstReaderHoldCount設置為1
                if (r == 0) {
                    firstReader = current;
                    firstReaderHoldCount = 1;
                } else if (firstReader == current) {
                    //如果firstReader是當前線程,則將firstReader的重入計數(shù)變量firstReaderHoldCount加1
                    firstReaderHoldCount++;
                } else {
                    //否則說明有至少兩個線程共享讀鎖,獲取共享鎖重入計數(shù)器HoldCounter
                    //從HoldCounter中拿到當前線程的線程變量cachedHoldCounter,將此線程的重入計數(shù)count加1
                    HoldCounter rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            //如果上面的if條件有一個都不滿足,則進入到這個方法里進行死循環(huán)重新獲取
            return fullTryAcquireShared(current);
        }
        /**
         * 用于處理CAS操作state失敗和tryAcquireShared中未執(zhí)行獲取可重入鎖動作的full方法(補償方法?)
         * Full version of acquire for reads, that handles CAS misses
         * and reentrant reads not dealt with in tryAcquireShared.
         */
        final int fullTryAcquireShared(Thread current) {
            /*
             * 此代碼與tryAcquireShared中的代碼有部分相似的地方,
             * 但總體上更簡單,因為不會使tryAcquireShared與重試和延遲讀取保持計數(shù)之間的復雜判斷
             * This code is in part redundant with that in
             * tryAcquireShared but is simpler overall by not
             * complicating tryAcquireShared with interactions between
             * retries and lazily reading hold counts.
             */
            HoldCounter rh = null;
            //死循環(huán)
            for (;;) {
                //獲取讀寫鎖狀態(tài)
                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;
                    }
                }
                /**
                 *下面是既沒有線程獲取寫鎖,當前線程又不需要阻塞的情況
                 */
                //重入次數(shù)等于最大重入次數(shù),拋異常
                if (sharedCount(c) == MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                //如果執(zhí)行CAS操作成功將讀寫鎖的重入計數(shù)加1,則對當前持有這個共享讀鎖的線程的重入計數(shù)加1,然后返回成功
                if (compareAndSetState(c, c + SHARED_UNIT)) {
                    if (sharedCount(c) == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        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;
                }
            }
        }

接下來是釋放讀鎖的源碼:

/**
  * Releases in shared mode.  Implemented by unblocking one or more
  * threads if {@link #tryReleaseShared} returns true.
  *
  * @param arg the release argument.  This value is conveyed to
  *        {@link #tryReleaseShared} but is otherwise uninterpreted
  *        and can represent anything you like.
  * @return the value returned from {@link #tryReleaseShared}
  */
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {//嘗試釋放一次共享鎖計數(shù)
        doReleaseShared();//真正釋放鎖
        return true;
    }
        return false;
}
/**
 *此方法表示讀鎖線程釋放鎖。
 *首先判斷當前線程是否為第一個讀線程firstReader,
 *若是,則判斷第一個讀線程占有的資源數(shù)firstReaderHoldCount是否為1,
  若是,則設置第一個讀線程firstReader為空,否則,將第一個讀線程占有的資源數(shù)firstReaderHoldCount減1;
  若當前線程不是第一個讀線程,
  那么首先會獲取緩存計數(shù)器(上一個讀鎖線程對應的計數(shù)器 ),
  若計數(shù)器為空或者tid不等于當前線程的tid值,則獲取當前線程的計數(shù)器,
  如果計數(shù)器的計數(shù)count小于等于1,則移除當前線程對應的計數(shù)器,
  如果計數(shù)器的計數(shù)count小于等于0,則拋出異常,之后再減少計數(shù)即可。
  無論何種情況,都會進入死循環(huán),該循環(huán)可以確保成功設置狀態(tài)state
 */
protected final boolean tryReleaseShared(int unused) {
      // 獲取當前線程
      Thread current = Thread.currentThread();
      if (firstReader == current) { // 當前線程為第一個讀線程
          // assert firstReaderHoldCount > 0;
         if (firstReaderHoldCount == 1) // 讀線程占用的資源數(shù)為1
              firstReader = null;
          else // 減少占用的資源
              firstReaderHoldCount--;
     } else { // 當前線程不為第一個讀線程
         // 獲取緩存的計數(shù)器
         HoldCounter rh = cachedHoldCounter;
         if (rh == null || rh.tid != getThreadId(current)) // 計數(shù)器為空或者計數(shù)器的tid不為當前正在運行的線程的tid
             // 獲取當前線程對應的計數(shù)器
             rh = readHolds.get();
         // 獲取計數(shù)
         int count = rh.count;
         if (count <= 1) { // 計數(shù)小于等于1
             // 移除
             readHolds.remove();
             if (count <= 0) // 計數(shù)小于等于0,拋出異常
                 throw unmatchedUnlockException();
         }
         // 減少計數(shù)
         --rh.count;
     }
     for (;;) { // 死循環(huán)
         // 獲取狀態(tài)
         int c = getState();
         // 獲取狀態(tài)
         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;
     }
 }
 /**真正釋放鎖
  * Release action for shared mode -- signals successor and ensures
  * propagation. (Note: For exclusive mode, release just amounts
  * to calling unparkSuccessor of head if it needs signal.)
  */
private void doReleaseShared() {
        /*
         * Ensure that a release propagates, even if there are other
         * in-progress acquires/releases.  This proceeds in the usual
         * way of trying to unparkSuccessor of head if it needs
         * signal. But if it does not, status is set to PROPAGATE to
         * ensure that upon release, propagation continues.
         * Additionally, we must loop in case a new node is added
         * while we are doing this. Also, unlike other uses of
         * unparkSuccessor, we need to know if CAS to reset status
         * fails, if so rechecking.
         */
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            if (h == head)                   // loop if head changed
                break;
        }
    }

通過分析可以看出,當一個線程持有讀鎖時,這個線程是無法獲取到寫鎖的,因為無論是否持有讀鎖,如果當前讀鎖被占用,它獲取寫鎖的嘗試都會失敗通過當前線程。

接下來,當一個線程持有寫鎖時,該線程可以繼續(xù)獲取讀鎖。在獲取讀鎖的過程中,如果寫鎖被占用,只有當前線程占用了寫鎖,才能獲取到讀鎖。

3、LongAdder

在高并發(fā)場景下,直接對Integer類型的整數(shù)進行i++并不能保證操作的原子性,導致線程安全問題。為此,我們在juc中使用了AtomicInteger,它是一個提供原子操作的Integer類。在內部,線程安全是通過 CAS 實現(xiàn)的。但是,當大量線程同時訪問一個鎖時,由于大量線程未能執(zhí)行CAS操作,就會發(fā)生自旋。

導致CPU資源消耗過大,執(zhí)行效率低下。 Doug Lea 對此并不滿意,于是他在 JDK 1.8 中對 CAS 進行了優(yōu)化,并提供了 LongAdder,它基于 CAS 段鎖的思想。

LongAdder是基于CAS和volatile實現(xiàn)的,由Unsafe提供。 LongAdder 的 Striped64 父類中維護了一個基變量和一個Cell數(shù)組。當多個線程對一個變量進行操作時,首先對這個基變量進行CAS操作。當檢測到其他線程時,將使用元胞數(shù)組。

例如,當即將更新 base 時,檢測到更多的線程。即casBase方法更新基值失敗,自動使用cell數(shù)組,每個線程對應一個cell,在每個線程中對cell進行CAS操作。

這樣就可以在多個值之間分擔單個值的更新壓力,降低單個值的“熱度”。也減少了大量線程的自旋,提高并發(fā)效率,分散并發(fā)壓力。這種段鎖需要額外的內存單元,但是在高并發(fā)場景下成本幾乎可以忽略不計。段鎖是一種杰出的優(yōu)化方法。 juc中的ConcurrentHashMap也是基于段鎖來保證讀寫的線程安全。

到此這篇關于淺談一下Java中的幾種JVM級別的鎖的文章就介紹到這了,更多相關JVM級別的鎖內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring?Lifecycle的使用小結

    Spring?Lifecycle的使用小結

    這篇文章主要介紹了Spring?Lifecycle的使用,本文結合實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • 基于Java的guava開源庫工具類

    基于Java的guava開源庫工具類

    guava是谷歌基于java封裝好的開源庫,這篇文章主要通過介紹幾個好用的guava工具類,感興趣的朋友可以參考下面文章內容
    2021-09-09
  • springboot 1.5.2 集成kafka的簡單例子

    springboot 1.5.2 集成kafka的簡單例子

    本篇文章主要介紹了springboot 1.5.2 集成kafka的簡單例子 ,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-11-11
  • servlet之session工作原理簡介_動力節(jié)點Java學院整理

    servlet之session工作原理簡介_動力節(jié)點Java學院整理

    這篇文章主要介紹了servlet之session工作原理簡介,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-07-07
  • spring消息轉換器使用詳解

    spring消息轉換器使用詳解

    這篇文章主要為大家詳細介紹了spring消息轉換器的使用,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-07-07
  • Java事件處理機制(自定義事件)實例詳解

    Java事件處理機制(自定義事件)實例詳解

    這篇文章主要介紹了Java事件處理機制(自定義事件)實例詳解的相關資料,需要的朋友可以參考下
    2016-12-12
  • Java基礎:徹底搞懂java多線程

    Java基礎:徹底搞懂java多線程

    篇文章主要介紹了Java多線程的相關資料,幫助大家更好的理解和學習Java線程相關知識,感興趣的朋友可以了解下,希望能給你帶來幫助
    2021-08-08
  • SpringBoot的@Value注解如何設置默認值

    SpringBoot的@Value注解如何設置默認值

    這篇文章主要介紹了SpringBoot的@Value注解如何設置默認值問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Java雪花算法的原理和實現(xiàn)方法

    Java雪花算法的原理和實現(xiàn)方法

    這篇文章主要介紹了Java雪花算法的原理和實現(xiàn)方法,雪花算法是一種分布式唯一ID生成算法,可以生成全局唯一的ID標識符,就像自然界中雪花一般沒有相同的雪花,下面將詳細介紹,感興趣的可以學習一下
    2023-10-10
  • springboot程序啟動慢-未配置hostname的解決

    springboot程序啟動慢-未配置hostname的解決

    這篇文章主要介紹了springboot程序啟動慢-未配置hostname的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08

最新評論