JDK中有關鎖的一些接口和類

      網友投稿 672 2025-04-02

      眾所周知,JDK中關于并發的類大多都在java.util.concurrent(以下簡稱juc)包下。而juc.locks包看名字就知道,是提供了一些并發鎖的工具類的。前面我們介紹的AQS(AbstractQueuedSynchronizer)就是在這個包下。下面分別介紹一下這個包下的類和接口以及它們之間的關系。

      抽象類AQS/AQLS/AOS

      這三個抽象類有一定的關系,所以這里放到一起講。

      首先我們看AQS(AbstractQueuedSynchronizer),之前專門有章節介紹這個類,它是在JDK 1.5 發布的,提供了一個“隊列同步器”的基本功能實現。而AQS里面的“資源”是用一個int類型的數據來表示的,有時候我們的業務需求資源的數量超出了int的范圍,所以在JDK 1.6 中,多了一個AQLS(AbstractQueuedLongSynchronizer)。它的代碼跟AQS幾乎一樣,只是把資源的類型變成了long類型。

      AQS和AQLS都繼承了一個類叫AOS(AbstractOwnableSynchronizer)。這個類也是在JDK 1.6 中出現的。這個類只有幾行簡單的代碼。從源碼類上的注釋可以知道,它是用于表示鎖與持有者之間的關系(獨占模式)。可以看一下它的主要方法:

      // 獨占模式,鎖的持有者 private transient Thread exclusiveOwnerThread; // 設置鎖持有者 protected final void setExclusiveOwnerThread(Thread t) { exclusiveOwnerThread = t; } // 獲取鎖的持有線程 protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; }

      接口Condition/Lock/ReadWriteLock

      juc.locks包下共有三個接口:Condition、Lock、ReadWriteLock。其中,Lock和ReadWriteLock從名字就可以看得出來,分別是鎖和讀寫鎖的意思。Lock接口里面有一些獲取鎖和釋放鎖的方法聲明,而ReadWriteLock里面只有兩個方法,分別返回“讀鎖”和“寫鎖”:

      public interface ReadWriteLock { Lock readLock(); Lock writeLock(); }

      Lock接口中有一個方法是可以獲得一個Condition:

      Condition newCondition();

      之前我們提到了每個對象都可以用繼承自Object的wait/notify方法來實現等待/通知機制。而Condition接口也提供了類似Object監視器的方法,通過與Lock配合來實現等待/通知模式。

      JDK中有關鎖的一些接口和類

      那為什么既然有Object的監視器方法了,還要用Condition呢?這里有一個二者簡單的對比:

      Condition和Object的wait/notify基本相似。其中,Condition的await方法對應的是Object的wait方法,而Condition的signal/signalAll方法則對應Object的notify/notifyAll()。但Condition類似于Object的等待/通知機制的加強版。我們來看看主要的方法:

      ReentrantLock

      ReentrantLock是一個非抽象類,它是Lock接口的JDK默認實現,實現了鎖的基本功能。從名字上看,它是一個”可重入“鎖,從源碼上看,它內部有一個抽象類Sync,是繼承了AQS,自己實現的一個同步器。同時,ReentrantLock內部有兩個非抽象類NonfairSync和FairSync,它們都繼承了Sync。從名字上看得出,分別是”非公平同步器“和”公平同步器“的意思。這意味著ReentrantLock可以支持”公平鎖“和”非公平鎖“。

      通過看這兩個同步器的源碼可以發現,它們的實現都是”獨占“的。都調用了AOS的setExclusiveOwnerThread方法,所以ReentrantLock的鎖是”獨占“的,也就是說,它的鎖都是”排他鎖“,不能共享。

      在ReentrantLock的構造方法里,可以傳入一個boolean類型的參數,來指定它是否是一個公平鎖,默認情況下是非公平的。這個參數一旦實例化后就不能修改,只能通過isFair()方法來查看。

      ReentrantReadWriteLock

      這個類也是一個非抽象類,它是ReadWriteLock接口的JDK默認實現。它與ReentrantLock的功能類似,同樣是可重入的,支持非公平鎖和公平鎖。不同的是,它還支持”讀寫鎖“。

      ReentrantReadWriteLock內部的結構大概是這樣:

      // 內部結構 private final ReentrantReadWriteLock.ReadLock readerLock; private final ReentrantReadWriteLock.WriteLock writerLock; final Sync sync; abstract static class Sync extends AbstractQueuedSynchronizer { // 具體實現 } static final class NonfairSync extends Sync { // 具體實現 } static final class FairSync extends Sync { // 具體實現 } public static class ReadLock implements Lock, java.io.Serializable { private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } // 具體實現 } public static class WriteLock implements Lock, java.io.Serializable { private final Sync sync; protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } // 具體實現 } // 構造方法,初始化兩個鎖 public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } // 獲取讀鎖和寫鎖的方法 public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }

      可以看到,它同樣是內部維護了兩個同步器。且維護了兩個Lock的實現類ReadLock和WriteLock。從源碼可以發現,這兩個內部類用的是外部類的同步器。

      ReentrantReadWriteLock實現了讀寫鎖,但它有一個小弊端,就是在“寫”操作的時候,其它線程不能寫也不能讀。我們稱這種現象為“寫饑餓”,將在后文的StampedLock類繼續討論這個問題。

      StampedLock

      StampedLock類是在Java 8 才發布的,也是Doug Lea大神所寫,有人號稱它為鎖的性能之王。它沒有實現Lock接口和ReadWriteLock接口,但它其實是實現了“讀寫鎖”的功能,并且性能比ReentrantReadWriteLock更高。StampedLock還把讀鎖分為了“樂觀讀鎖”和“悲觀讀鎖”兩種。

      前面提到了ReentrantReadWriteLock會發生“寫饑餓”的現象,但StampedLock不會。它是怎么做到的呢?它的核心思想在于,在讀的時候如果發生了寫,應該通過重試的方式來獲取新的值,而不應該阻塞寫操作。這種模式也就是典型的無鎖編程思想,和CAS自旋的思想一樣。這種操作方式決定了StampedLock在讀線程非常多而寫線程非常少的場景下非常適用,同時還避免了寫饑餓情況的發生。

      這里篇幅有限,就不介紹StampedLock的源碼了,只是分析一下官方提供的用法(在JDK源碼類聲明的上方或Javadoc里可以找到)。

      class Point { private double x, y; private final StampedLock sl = new StampedLock(); // 寫鎖的使用 void move(double deltaX, double deltaY) { long stamp = sl.writeLock(); // 獲取寫鎖 try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); // 釋放寫鎖 } } // 樂觀讀鎖的使用 double distanceFromOrigin() { long stamp = sl.tryOptimisticRead(); // 獲取樂觀讀鎖 double currentX = x, currentY = y; if (!sl.validate(stamp)) { // //檢查樂觀讀鎖后是否有其他寫鎖發生,有則返回false stamp = sl.readLock(); // 獲取一個悲觀讀鎖 try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); // 釋放悲觀讀鎖 } } return Math.sqrt(currentX * currentX + currentY * currentY); } // 悲觀讀鎖以及讀鎖升級寫鎖的使用 void moveIfAtOrigin(double newX, double newY) { long stamp = sl.readLock(); // 悲觀讀鎖 try { while (x == 0.0 && y == 0.0) { // 讀鎖嘗試轉換為寫鎖:轉換成功后相當于獲取了寫鎖,轉換失敗相當于有寫鎖被占用 long ws = sl.tryConvertToWriteLock(stamp); if (ws != 0L) { // 如果轉換成功 stamp = ws; // 讀鎖的票據更新為寫鎖的 x = newX; y = newY; break; } else { // 如果轉換失敗 sl.unlockRead(stamp); // 釋放讀鎖 stamp = sl.writeLock(); // 強制獲取寫鎖 } } } finally { sl.unlock(stamp); // 釋放所有鎖 } } }

      樂觀讀鎖的意思就是先假定在這個鎖獲取期間,共享變量不會被改變,既然假定不會被改變,那就不需要上鎖。在獲取樂觀讀鎖之后進行了一些操作,然后又調用了validate方法,這個方法就是用來驗證tryOptimisticRead之后,是否有寫操作執行過,如果有,則獲取一個悲觀讀鎖,這里的悲觀讀鎖和ReentrantReadWriteLock中的讀鎖類似,也是個共享鎖。

      可以看到,StampedLock獲取鎖會返回一個long類型的變量,釋放鎖的時候再把這個變量傳進去。簡單看看源碼:

      // 用于操作state后獲取stamp的值 private static final int LG_READERS = 7; private static final long RUNIT = 1L; //0000 0000 0001 private static final long WBIT = 1L << LG_READERS; //0000 1000 0000 private static final long RBITS = WBIT - 1L; //0000 0111 1111 private static final long RFULL = RBITS - 1L; //0000 0111 1110 private static final long ABITS = RBITS | WBIT; //0000 1111 1111 private static final long SBITS = ~RBITS; //1111 1000 0000 // 初始化時state的值 private static final long ORIGIN = WBIT << 1; //0001 0000 0000 // 鎖共享變量state private transient volatile long state; // 讀鎖溢出時用來存儲多出的讀鎖 private transient int readerOverflow;

      StampedLock用這個long類型的變量的前7位(LG_READERS)來表示讀鎖,每獲取一個悲觀讀鎖,就加1(RUNIT),每釋放一個悲觀讀鎖,就減1。而悲觀讀鎖最多只能裝128個(7位限制),很容易溢出,所以用一個int類型的變量來存儲溢出的悲觀讀鎖。

      寫鎖用state變量剩下的位來表示,每次獲取一個寫鎖,就加0000 1000 0000(WBIT)。需要注意的是,寫鎖在釋放的時候,并不是減WBIT,而是再加WBIT。這是為了讓每次寫鎖都留下痕跡,解決CAS中的ABA問題,也為樂觀鎖檢查變化validate方法提供基礎。

      樂觀讀鎖就比較簡單了,并沒有真正改變state的值,而是在獲取鎖的時候記錄state的寫狀態,在操作完成后去檢查state的寫狀態部分是否發生變化,上文提到了,每次寫鎖都會留下痕跡,也是為了這里樂觀鎖檢查變化提供方便。

      總的來說,StampedLock的性能是非常優異的,基本上可以取代ReentrantReadWriteLock的作用。

      Java JDK 任務調度

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

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

      上一篇:API及SDK介紹
      下一篇:僅用3年躍升華為云教育類目伙伴TOP2
      相關文章
      亚洲成人在线电影| 在线播放亚洲第一字幕| 亚洲色偷偷综合亚洲AVYP| 欧洲亚洲国产精华液| 亚洲国产精品综合久久2007| 亚洲高清视频在线观看| 久久香蕉国产线看观看亚洲片| 亚洲裸男gv网站| 亚洲欧洲久久久精品| 亚洲免费无码在线| 亚洲国产午夜中文字幕精品黄网站| 日本亚洲高清乱码中文在线观看| 亚洲人成网站免费播放| 亚洲精品国产摄像头| 国产亚洲美女精品久久久久| 亚洲Av无码国产一区二区| 狼人大香伊蕉国产WWW亚洲| 亚洲AV无码一区二区三区牲色| 亚洲影院天堂中文av色| 亚洲午夜理论片在线观看| 亚洲字幕AV一区二区三区四区| 亚洲熟妇av午夜无码不卡| 亚洲精品成a人在线观看夫| 亚洲Av永久无码精品黑人 | 亚洲av无码不卡| 亚洲男人第一av网站| 亚洲精品国产手机| 亚洲一区动漫卡通在线播放| 亚洲最大福利视频| 亚洲中文字幕无码爆乳app| 亚洲精品自偷自拍无码| 久久亚洲精品无码av| 亚洲黄黄黄网站在线观看| 国产午夜亚洲精品理论片不卡| 亚洲色精品88色婷婷七月丁香 | 久久亚洲最大成人网4438| 最新国产精品亚洲| 色偷偷噜噜噜亚洲男人| 亚洲日韩在线中文字幕第一页| 亚洲AV无码乱码在线观看性色扶 | 亚洲成综合人影院在院播放|