Java中的鎖 重入鎖ReentrantLock

      網友投稿 607 2025-04-02

      重入鎖ReentrantLock指的是支持同一個線程對資源的重復加鎖。ReentrantLock中有公平鎖和非公平鎖的兩種實現。

      synchronized關鍵字支持隱式的重入;當一個線程獲取到鎖時,是支持這個線程多次獲取這個鎖的,不會出現自己阻塞自己的情況,并且我們開發過程中對于synchronized關鍵字也不需要關心鎖的釋放。舉個遞歸的例子我們來看synchronized關鍵字對鎖的重入。

      代碼示例

      package com.lizba.p6; /** *

      * synchronized鎖重入測試 *

      * * @Author: Liziba * @Date: 2021/6/21 21:45 */ public class SynchronizedTest { public static void main(String[] args) { int sum = cal(0); System.out.println(sum); } /** * 簡單遞歸重入,遞歸十次 * @param i * @return */ private static synchronized int cal(int i) { if (i < 10) { return cal(++i); } return i; } }

      輸出結果為10,在這個過程中main線程重復進入synchronized關鍵字修飾的cal(int i)方法。因此也證明了上面的說法正確。

      ReentrantLock基于AQS和Lock來實現的,那如果是我們ReentrantLock要實現可重入,需要解決和實現如下兩個問題:

      同一個線程多次獲取鎖,則需要判斷當前來獲取鎖的線程和占有鎖的線程是否為同一個線程

      多次獲取鎖,則需要多次釋放這個鎖,可以通過一個計數器累加和自減來記錄鎖的重復獲取與釋放

      ReentrantLock中有兩種重入鎖的實現,分別是:

      NonfairSync-非公平鎖

      FairSync-公平鎖

      公平鎖和非公平鎖的本質區別就在于,獲取鎖的順序是否符合FIFO,對于公平鎖來說先加入同步隊列等待的線程,必將會先獲取到同步狀態(鎖),對于非公平鎖來說,獲取到鎖的順序不確定。

      在進行源碼分析之前,先來看看ReentrantLock是如何使用的,ReentrantLock的使用非常簡單,示例代碼如下:

      package com.lizba.p6; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** *

      * ReentrantLock使用示例代碼 *

      * * @Author: Liziba * @Date: 2021/6/21 22:09 */ public class ReentrantLockDemo { /** 初始化一個非公平鎖,ReentrantLock的默認實現是非公平鎖 */ private static Lock lock = new ReentrantLock(); public static void main(String[] args) { new Thread(() -> testReentrantLock(), "Thread A").start(); new Thread(() -> testReentrantLock(), "Thread B").start(); } /** * 假設為獲取鎖執行的相關業務邏輯方法 */ private static void testReentrantLock() { // 獲取鎖要在try值外,如果獲取鎖過程中異常,不會無故釋放鎖 lock.lock(); try { System.out.println(Thread.currentThread().getName() + ":獲取了鎖"); Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { // 釋放鎖在finally代碼塊中 lock.unlock(); System.out.println(Thread.currentThread().getName() + ":釋放了鎖"); } } }

      如上簡單使用的案例,Thread A和Thread B輸出的結果如下(這里是非公平鎖不要被現在的順序迷惑):

      Sync—ReentrantLock組合的自定義同步器抽象

      /** * ReentrantLock內部類Sync,也是其內部組合實現的自定義同步器的抽象 */ abstract static class Sync extends AbstractQueuedSynchronizer { private static final long serialVersionUID = -5179523762034025860L; /** * 定義獲取鎖的抽象方法,由NonfairSync和FairSync去實現各自獲取鎖的方式 */ abstract void lock(); /** * NonfairSync中tryAcquire調用的方法 */ final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 如果當前共享狀態未被其他線程占用 if (c == 0) { // 嘗試通過CAS占有當前共享狀態 if (compareAndSetState(0, acquires)) { // 設置共享狀態持有線程為當前線程 setExclusiveOwnerThread(current); return true; } } // 如果共享狀態已被占用,則判斷當前占用共享狀態的線程是否就是當前線程 else if (current == getExclusiveOwnerThread()) { // 如果是則自增獲取次數,設值state int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } /** * 釋放共享狀態 */ protected final boolean tryRelease(int releases) { // 計算減少后的值state int c = getState() - releases; // 判斷當前線程和持有共享狀態的線程是否是同一個線程,不是則拋出異常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // 如果state遞減后的值為0了,表示線程釋放完共享狀態,需要情況持有共享狀態的線程變量 if (c == 0) { free = true; setExclusiveOwnerThread(null); } // 設置state setState(c); return free; } /** * 判斷占用共享狀態的線程是否是當前線程 */ protected final boolean isHeldExclusively() { return getExclusiveOwnerThread() == Thread.currentThread(); } /** * 如果state不為0,則獲取共享狀態的持有線程,否則返回null */ final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } /** * 如果當前線程持有共享狀態,則返回state,否則返回0 */ final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } /** * 判斷當前共享狀態是否被持有 */ final boolean isLocked() { return getState() != 0; } private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } final ConditionObject newCondition() { return new ConditionObject(); } }

      NonfairSync源碼分析

      /** * 非公平鎖的代碼實現 */ static final class NonfairSync extends Sync { private static final long serialVersionUID = 7316153563782823691L; /** * */ final void lock() { // CAS設置共享狀態,返回true表示成功獲取共享狀態 if (compareAndSetState(0, 1)) // 設置當前線程為共享狀態的持有線程 setExclusiveOwnerThread(Thread.currentThread()); else // 否則調用AQS中的acquire(int arg)嘗試獲取同步狀態,失敗則加入等待隊列,自旋獲取共享狀態 acquire(1); } /** * 該方法在調用Sync中定義的nonfairTryAcquire方法,上面詳細講述了 * 主要是做線程重入判斷,并對state共享狀態值的增加(當獲取同步狀態的線程是持有同步狀態的線程也就是所說的重入) */ protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } }

      NonfairSync的tryRelease調用的是Sync中的tryRelease,上面在Sync源碼中詳細介紹了。假設線程A對共享狀態tryAcquire(1)了十次,那么線程A在調用tryRelease(1)的前9次,state的值依次遞減的同時一定會返回false,只有第十次也就是最后一調用tryRelease(1),同步狀態才會真正的釋放,方法返回true,持有共享狀態的線程置為null。

      /** * 公平鎖的代碼實現 */ static final class FairSync extends Sync { private static final long serialVersionUID = -3000897897090466540L; /** * 調用AQS中的acquire(int arg)嘗試獲取同步狀態,失敗則加入等待隊列,自旋獲取共享狀態 */ final void lock() { acquire(1); } /** * 公平鎖和非公平鎖的主要區別在于此方法 */ protected final boolean tryAcquire(int acquires) { // 獲取到當前線程 final Thread current = Thread.currentThread(); // 獲取當前同步狀態 int c = getState(); // 如果同步狀態為0,則說明當前同步狀態已完全釋放 if (c == 0) { // 1、hasQueuedPredecessors判斷當前節點是否存在前驅節點 // 2、如果不存在則CAS設置state的值 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // 前兩個都滿足則,設置同步狀態持有的線程為當前線程 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; } }

      FairSync能夠順序的獲取共享狀態,也就是保證加入同步隊列的順序和獲取到同步狀態的順序一致,依靠的是hasQueuedPredecessors()這個判斷當前節點是否存在前驅節點的判斷。

      package com.lizba.p6; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; /** *

      * 公平和非公平鎖測試 *

      * * @Author: Liziba * @Date: 2021/6/21 23:33 */ public class FairAndUnfairTest { /** 定義公平鎖 */ private static Lock fairLock = new ReentrantLockCustomize(true); /** 定義非公平鎖 */ private static Lock unfairLock = new ReentrantLockCustomize(false); /** * 測試公平鎖和非公平鎖 * @param lock */ private static void testFairAndUnfairLock(Lock lock) { for (int i = 1; i <= 5; i++) { new Job(lock, ""+i).start(); } } /** * 定義線程實現,打印當前線程和等待隊列中的線程 */ private static class Job extends Thread { private Lock lock; public Job(Lock lock,String name) { this.lock = lock; setName(name); } @Override public void run() { // 通過兩次輸出,來判斷是否與隊列中一致 for (int i = 0; i < 2; i++) { lock.lock(); try { System.out.println("獲取鎖的線程:" + Thread.currentThread().getName()); System.out.println("同步隊列中的線程:" + ((ReentrantLockCustomize)lock).getQueuedThreads().stream().map(t -> t.getName()).collect(Collectors.joining(","))); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } } /** * 自定義可重入鎖,主要新增getQueuedThreads()方法,用于獲取等待隊列中的線程 */ private static class ReentrantLockCustomize extends ReentrantLock { public ReentrantLockCustomize(boolean fair) { super(fair); } /** * 返回正在等待獲取鎖的線程列表,獲取的實現列表逆序輸出,反轉后則為FIFO隊列的原本順序 * * @return 等待隊列中的線程順序集合 */ public Collection getQueuedThreads() { List ts = new ArrayList<>(super.getQueuedThreads()); Collections.reverse(ts); return ts; } } }

      Java中的鎖 重入鎖ReentrantLock

      測試公平鎖

      // 測試公平鎖 testFairAndUnfairLock(fairLock);

      查看輸出

      同步隊列中等待的線程的順序為2、3、4、5此時輸出的結果為1、2、3、4、5和 1、2、3、4、5,按照同步隊列中等待的順序順序輸出,先進入同步隊列的先獲取到鎖。

      測試非公平鎖

      // 測試非公平鎖 testFairAndUnfairLock(unfairLock);

      查看輸出

      同步隊列中等待的線程順序為2、4、5、3當時線程1卻連續獲取了兩次鎖,因此非公平鎖是不能保證獲取鎖的順序的。

      存在問題:

      非公平鎖很明顯存在線程“饑餓”問題,也就是一個線程獲取到鎖后會繼續的再次獲取到鎖的可能性比較大,導致其他線程等待時間較長,那么為何ReentrantLock還有繼續設置其為默認實現呢?這個主要原因是,公平鎖會帶來大量的線程切換的開銷,而非公平鎖雖然可能會導致線程“饑餓”問題,但是其吞吐量是遠遠大于公平鎖的,相比之下非公平鎖優勢更大。

      Java

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:如何將一個表格按另外一個表格的順序排列(請問怎么把一個EXCEL的表格按另外一個表格順序來排列?)
      下一篇:excel表格中自動換行和強行換行有什么區別?
      相關文章
      亚洲人成电影网站色www| 一本色道久久88亚洲精品综合 | 亚洲精品无码久久久影院相关影片| 国产尤物在线视精品在亚洲| 亚洲av无码成人精品区一本二本| 亚洲妇女熟BBW| 亚洲一本之道高清乱码| 亚洲另类春色校园小说| 亚洲一卡2卡4卡5卡6卡残暴在线| 亚洲欧洲日本精品| 亚洲国产精品久久久久秋霞影院| 亚洲精品午夜久久久伊人| 久久精品国产亚洲AV嫖农村妇女| 精品日韩亚洲AV无码 | 国产AV日韩A∨亚洲AV电影| 国产亚洲成在线播放va| 亚洲&#228;v永久无码精品天堂久久| 人人狠狠综合久久亚洲| 亚洲Av无码乱码在线播放| 亚洲国产精品无码久久青草| 亚洲日韩人妻第一页| 亚洲精品无码专区在线在线播放| 亚洲欧洲日产国码无码网站| 亚洲av永久无码精品国产精品| 久久精品亚洲综合| 亚洲视频一区二区在线观看| 亚洲成人福利在线| 亚洲综合色丁香婷婷六月图片| 亚洲AV永久无码精品一福利| xvideos亚洲永久网址| 久久精品国产亚洲Aⅴ香蕉| 国产亚洲成AV人片在线观黄桃 | 亚洲日本va一区二区三区| 亚洲a∨无码一区二区| 国产成人亚洲精品影院| 亚洲国产另类久久久精品黑人 | 亚洲一区二区三区日本久久九| 亚洲国产精品网站久久| 亚洲人成综合网站7777香蕉| 大桥未久亚洲无av码在线| 国产亚洲一区二区三区在线不卡 |