1. 程式人生 > >Java併發21:Lock系列-ReadWriteLock介面和ReentrantReadWriteLock類基本方法學習例項

Java併發21:Lock系列-ReadWriteLock介面和ReentrantReadWriteLock類基本方法學習例項

本章主要學習讀寫鎖

關於讀寫鎖,在《 Java併發18》中已經學習過:

  • synchronized關鍵字只提供了一種鎖,即互斥鎖。
  • java.util.concurretn.locks包不僅通過Lock介面提供了與前者類似的互斥鎖,而且還通過ReadWriteLock介面提供了讀鎖和寫鎖。
    讀寫鎖最大的優勢在於讀鎖與讀鎖並不獨佔,提高了共享資源的使用效率。

本文有以下三部分內容:

  • 學習ReadWriteLock介面的原始碼
  • 學習ReetrantReadWriteLock類的原始碼
  • 例項編碼

1.ReadWriteLock介面

1.1.原始碼

/**
 * A {@code ReadWriteLock} maintains a pair of associated {@link
 * Lock locks}, one for read-only operations and one for writing.
 * The {@link #readLock read lock} may be held simultaneously by
 * multiple reader threads, so long as there are no writers.  The
 * {@link #writeLock write lock} is exclusive.
 *
 * <p>All {@code ReadWriteLock} implementations must guarantee that
 * the memory synchronization effects of {@code writeLock} operations
 * (as specified in the {@link Lock} interface) also hold with respect
 * to the associated {@code readLock}. That is, a thread successfully
 * acquiring the read lock will see all updates made upon previous
 * release of the write lock.
 *
 * <p>A read-write lock allows for a greater level of concurrency in
 * accessing shared data than that permitted by a mutual exclusion lock.
 * It exploits the fact that while only a single thread at a time (a
 * <em>writer</em> thread) can modify the shared data, in many cases any
 * number of threads can concurrently read the data (hence <em>reader</em>
 * threads).
 * In theory, the increase in concurrency permitted by the use of a read-write
 * lock will lead to performance improvements over the use of a mutual
 * exclusion lock. In practice this increase in concurrency will only be fully
 * realized on a multi-processor, and then only if the access patterns for
 * the shared data are suitable.
 *
 * <p>Whether or not a read-write lock will improve performance over the use
 * of a mutual exclusion lock depends on the frequency that the data is
 * read compared to being modified, the duration of the read and write
 * operations, and the contention for the data - that is, the number of
 * threads that will try to read or write the data at the same time.
 * For example, a collection that is initially populated with data and
 * thereafter infrequently modified, while being frequently searched
 * (such as a directory of some kind) is an ideal candidate for the use of
 * a read-write lock. However, if updates become frequent then the data
 * spends most of its time being exclusively locked and there is little, if any
 * increase in concurrency. Further, if the read operations are too short
 * the overhead of the read-write lock implementation (which is inherently
 * more complex than a mutual exclusion lock) can dominate the execution
 * cost, particularly as many read-write lock implementations still serialize
 * all threads through a small section of code. Ultimately, only profiling
 * and measurement will establish whether the use of a read-write lock is
 * suitable for your application.
 *
 *
 * <p>Although the basic operation of a read-write lock is straight-forward,
 * there are many policy decisions that an implementation must make, which
 * may affect the effectiveness of the read-write lock in a given application.
 * Examples of these policies include:
 * <ul>
 * <li>Determining whether to grant the read lock or the write lock, when
 * both readers and writers are waiting, at the time that a writer releases
 * the write lock. Writer preference is common, as writes are expected to be
 * short and infrequent. Reader preference is less common as it can lead to
 * lengthy delays for a write if the readers are frequent and long-lived as
 * expected. Fair, or &quot;in-order&quot; implementations are also possible.
 *
 * <li>Determining whether readers that request the read lock while a
 * reader is active and a writer is waiting, are granted the read lock.
 * Preference to the reader can delay the writer indefinitely, while
 * preference to the writer can reduce the potential for concurrency.
 *
 * <li>Determining whether the locks are reentrant: can a thread with the
 * write lock reacquire it? Can it acquire a read lock while holding the
 * write lock? Is the read lock itself reentrant?
 *
 * <li>Can the write lock be downgraded to a read lock without allowing
 * an intervening writer? Can a read lock be upgraded to a write lock,
 * in preference to other waiting readers or writers?
 *
 * </ul>
 * You should consider all of these things when evaluating the suitability
 * of a given implementation for your application.
 *
 * @see
ReentrantReadWriteLock * @see Lock * @see ReentrantLock * * @since 1.5 * @author Doug Lea */
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading */ Lock readLock(); /** * Returns the lock used for writing. * * @return
the lock used for writing */
Lock writeLock(); }

1.2.原始碼翻譯

上面的原始碼翻譯如下:

一個ReadWriteLock鎖包含一對關聯的鎖,一個鎖用於只讀操作,另一個鎖用於寫操作

  • 只要沒有執行緒進行寫操作,多個執行緒可以同時持有這個只讀鎖readLock
  • 寫鎖writeLock是獨佔的。

所有ReadWriteLock介面的實現類必須保證記憶體同步效果:所有寫鎖writeLock的相關操作都對只讀鎖readLock可見。
也就是說,如果一個執行緒成功的獲取了只讀鎖readLock,那麼這個執行緒可以看到上個寫鎖writeLock

所做的所有修改。

相較於互斥鎖讀寫鎖提供了更高級別的對共享資料的併發訪問機制。
它響應了一個事實:雖然在同一時刻只有一個執行緒可以修改共享資料,但在很多情況下,多個執行緒可以併發的讀取共享資料。
理論上,使用讀寫鎖會比使用互斥鎖將會在併發資料訪問上有更好的效能體現。
實際上,這種併發效能的提高只能在多核處理器上,且只有在對共享資料的訪問模式合適時是才能有所體現。

讀寫鎖是否能夠提高併發效能取決於讀資料寫資料的比例、讀操作和寫操作的持續時間以及資料爭用情況(即試圖同時讀取或寫入資料的執行緒數量)。例如:

  • 一個通過初始化進行資料填充的集合,在不經常修改的情況下被頻繁搜尋,這就是一個使用讀資料的理想選擇。
  • 如果更新操作十分頻繁,程式將花費大部分時間進行互斥鎖定,那麼對併發效能的提升將會很小。

最終,只有經過配置和測試,才能確定讀寫鎖是否適合於您的應用程式。

儘管讀寫鎖的操作很簡單,但是為了保證它在程式中的有效性,這裡有很多策略需要決斷。這些策略的示例如下:

當一個寫鎖釋放時,如果同時有讀執行緒寫執行緒在等待鎖,這時要將鎖授予讀執行緒還是和寫執行緒

  • 選擇將鎖授予寫鎖是常見的:因為寫操作耗時很短並且發生概率很低,。
  • 選擇將鎖授予讀鎖是不常見的:因為如果當讀操作很頻繁並且耗時都很長,這將會導致寫操作長時間的等待。
  • 使用公平或者有序的實現方式,也是可能的。

當一個讀執行緒整在活動,且一個寫執行緒正在等待時,這時,是否要將鎖授予其他的讀執行緒

  • 如果選擇將鎖授予讀執行緒,則可能會導致寫執行緒無限期的等待。
  • 如果選擇將鎖授予讀執行緒,則會降低併發效能的潛力。

鎖是否是可重入的?

  • 一個持有過寫鎖的執行緒能夠重新獲取寫鎖
  • 一個持有寫鎖的執行緒能夠同時獲得讀鎖
  • 讀鎖本身是否可重入?

寫鎖讀鎖是否可以相關轉換?

  • 一個寫鎖是否可以降級為一個不允許其他寫執行緒插入的讀鎖
  • 一個讀鎖是否可以升級並優先於其他的讀執行緒寫執行緒寫鎖

在評估一個給定的實現類是否適合您的程式時,您應該考慮以上的所有問題。

1.3.總結

對上面的翻譯總結如下:

1.讀鎖與寫鎖

  • 一個ReadWriteLock鎖包含一對關聯的鎖:讀鎖寫鎖
  • 讀鎖readLock是共享的。
  • 寫鎖writeLock是獨佔的。

2.理論上,讀寫鎖互斥鎖有更好的效能體現的。

3.讀寫鎖更適用於讀多寫少的情景。

4.實際上,讀寫鎖是否能夠帶來效能的提升,是需要實際的測試與配置的。

2.ReetrantReadWriteLock類

2.1.原始碼

/**
 * An implementation of {@link ReadWriteLock} supporting similar
 * semantics to {@link ReentrantLock}.
 * <p>This class has the following properties:
 *
 * <ul>
 * <li><b>Acquisition order</b>
 *
 * <p>This class does not impose a reader or writer preference
 * ordering for lock access.  However, it does support an optional
 * <em>fairness</em> policy.
 *
 * <dl>
 * <dt><b><i>Non-fair mode (default)</i></b>
 * <dd>When constructed as non-fair (the default), the order of entry
 * to the read and write lock is unspecified, subject to reentrancy
 * constraints.  A nonfair lock that is continuously contended may
 * indefinitely postpone one or more reader or writer threads, but
 * will normally have higher throughput than a fair lock.
 *
 * <dt><b><i>Fair mode</i></b>
 * <dd>When constructed as fair, threads contend for entry using an
 * approximately arrival-order policy. When the currently held lock
 * is released, either the longest-waiting single writer thread will
 * be assigned the write lock, or if there is a group of reader threads
 * waiting longer than all waiting writer threads, that group will be
 * assigned the read lock.
 *
 * <p>A thread that tries to acquire a fair read lock (non-reentrantly)
 * will block if either the write lock is held, or there is a waiting
 * writer thread. The thread will not acquire the read lock until
 * after the oldest currently waiting writer thread has acquired and
 * released the write lock. Of course, if a waiting writer abandons
 * its wait, leaving one or more reader threads as the longest waiters
 * in the queue with the write lock free, then those readers will be
 * assigned the read lock.
 *
 * <p>A thread that tries to acquire a fair write lock (non-reentrantly)
 * will block unless both the read lock and write lock are free (which
 * implies there are no waiting threads).  (Note that the non-blocking
 * {@link ReadLock#tryLock()} and {@link WriteLock#tryLock()} methods
 * do not honor this fair setting and will immediately acquire the lock
 * if it is possible, regardless of waiting threads.)
 * <p>
 * </dl>
 *
 * <li><b>Reentrancy</b>
 *
 * <p>This lock allows both readers and writers to reacquire read or
 * write locks in the style of a {@link ReentrantLock}. Non-reentrant
 * readers are not allowed until all write locks held by the writing
 * thread have been released.
 *
 * <p>Additionally, a writer can acquire the read lock, but not
 * vice-versa.  Among other applications, reentrancy can be useful
 * when write locks are held during calls or callbacks to methods that
 * perform reads under read locks.  If a reader tries to acquire the
 * write lock it will never succeed.
 *
 * <li><b>Lock downgrading</b>
 * <p>Reentrancy also allows downgrading from the write lock to a read lock,
 * by acquiring the write lock, then the read lock and then releasing the
 * write lock. However, upgrading from a read lock to the write lock is
 * <b>not</b> possible.
 *
 * <li><b>Interruption of lock acquisition</b>
 * <p>The read lock and write lock both support interruption during lock
 * acquisition.
 *
 * <li><b>{@link Condition} support</b>
 * <p>The write lock provides a {@link Condition} implementation that
 * behaves in the same way, with respect to the write lock, as the
 * {@link Condition} implementation provided by
 * {@link ReentrantLock#newCondition} does for {@link ReentrantLock}.
 * This {@link Condition} can, of course, only be used with the write lock.
 *
 * <p>The read lock does not support a {@link Condition} and
 * {@code readLock().newCondition()} throws
 * {@code UnsupportedOperationException}.
 *
 * <li><b>Instrumentation</b>
 * <p>This class supports methods to determine whether locks
 * are held or contended. These methods are designed for monitoring
 * system state, not for synchronization control.
 * </ul>
 *
 * <p>Serialization of this class behaves in the same way as built-in
 * locks: a deserialized lock is in the unlocked state, regardless of
 * its state when serialized.
 *
 * <p><b>Sample usages</b>. Here is a code sketch showing how to perform
 * lock downgrading after updating a cache (exception handling is
 * particularly tricky when handling multiple locks in a non-nested
 * fashion):
 *
 * <pre> {@code
 * 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 {
 *         // Recheck state because another thread might have
 *         // acquired write lock and changed state before we did.
 *         if (!cacheValid) {
 *           data = ...
 *           cacheValid = true;
 *         }
 *         // Downgrade by acquiring read lock before releasing write lock
 *         rwl.readLock().lock();
 *       } finally {
 *         rwl.writeLock().unlock(); // Unlock write, still hold read
 *       }
 *     }
 *
 *     try {
 *       use(data);
 *     } finally {
 *       rwl.readLock().unlock();
 *     }
 *   }
 * }}</pre>
 *
 * ReentrantReadWriteLocks can be used to improve concurrency in some
 * uses of some kinds of Collections. This is typically worthwhile
 * only when the collections are expected to be large, accessed by
 * more reader threads than writer threads, and entail operations with
 * overhead that outweighs synchronization overhead. For example, here
 * is a class using a TreeMap that is expected to be large and
 * concurrently accessed.
 *
 *  <pre> {@code
 * class RWDictionary {
 *   private final Map<String, Data> m = new TreeMap<String, Data>();
 *   private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
 *   private final Lock r = rwl.readLock();
 *   private final Lock w = rwl.writeLock();
 *
 *   public Data get(String key) {
 *     r.lock();
 *     try { return m.get(key); }
 *     finally { r.unlock(); }
 *   }
 *   public String[] allKeys() {
 *     r.lock();
 *     try { return m.keySet().toArray(); }
 *     finally { r.unlock(); }
 *   }
 *   public Data put(String key, Data value) {
 *     w.lock();
 *     try { return m.put(key, value); }
 *     finally { w.unlock(); }
 *   }
 *   public void clear() {
 *     w.lock();
 *     try { m.clear(); }
 *     finally { w.unlock(); }
 *   }
 * }}</pre>
 *
 * <h3>Implementation Notes</h3>
 *
 * <p>This lock supports a maximum of 65535 recursive write locks
 * and 65535 read locks. Attempts to exceed these limits result in
 * {@link Error} throws from locking methods.
 *
 * @since 1.5
 * @author Doug Lea
 */
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {//...}

2.2.原始碼翻譯

ReentrantReadWriteLock類ReadWriteLock介面的實現,支援與之類似的語法。該類具有以下屬性:

1.獲取順序
這個類不會強行指定訪問鎖的讀寫順序,但是它支援一個可選的公平策略:

非公平模式(預設)
非公平模式下,進入讀鎖和寫鎖的順序取決於可重入約束,是不確定的。
非公平模式下,連續爭用可能會導致一個或者多個讀執行緒寫執行緒進入無限等待狀態。
不過,通常非公平鎖會比公平鎖有更高的吞吐量。

公平模式
公平模式下,進入讀鎖和寫鎖的順序使用一種近乎順序的策略。
噹噹前持有的鎖被釋放時,等待時間最長的單個寫執行緒會被授予寫鎖,或者如果有一組讀執行緒比所有的寫執行緒等待的時間都長,則這組讀執行緒將被授予鎖。

如果有寫執行緒持有鎖或者有寫執行緒正在等待鎖,試圖去獲取一個公平的讀鎖(不可重入)的讀執行緒將被阻塞。
這個讀執行緒不會獲得鎖,直到當前等待的所有寫執行緒獲取並釋放了鎖。
當然,如果寫執行緒放棄了等待,使得等待佇列中只剩下一個或者多個等待時間最長的讀執行緒,並且當前讀鎖可用,則這些讀執行緒將會被授予鎖。

除非當前的讀鎖寫鎖都是可用的,寫執行緒嘗試去獲取一個公平的寫鎖(不可重入)才不會被阻塞。

2.可重入性

ReentrantReadWriteLock類定義的鎖,允許讀執行緒寫執行緒ReentrantLock的形式去獲取讀鎖寫鎖

直到所有持有鎖的寫執行緒釋放鎖,不可重入的讀執行緒才會被允許獲取鎖。

此外,寫執行緒可以獲取讀鎖,反過來,讀執行緒不可以獲取寫鎖

3.可降級性

可重入性也允許寫鎖降級成為讀鎖:首先獲取寫鎖,然後獲取讀鎖,然後釋放寫鎖

但是,從讀鎖升級為寫鎖是不可能的。

4.可中斷性

在嘗試獲取鎖的過程中,讀鎖寫鎖都可以被中斷。

5.支援Condition

就像ReentrantLock一樣,寫鎖支援Condition操作。

當然,這種Condition操作,只能被應用在寫鎖上。

讀鎖不支援Condition操作,readLock().newCondition()會丟擲一個UnsupportedOperationException異常

6.狀態儀表盤

ReentrantReadWriteLock類提供了方法用於檢視鎖是被持有還是被爭用。

這些方法是為監視系統狀態而設計的,而不是用於同步控制。

這個類的序列化行為與內建鎖的方式相同:當鎖被序列化時,會無視其狀態,一個序列化的鎖必定是解鎖狀態。

示例用法:

下面的程式碼展示瞭如何在更新快取之後,將寫鎖降級為讀鎖(當以非巢狀的形式處理多個鎖時,異常處理特別棘手):

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 {
        // Recheck state because another thread might have
        // acquired write lock and changed state before we did.
        if (!cacheValid) {
          data = ...
          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();//釋放讀鎖
    }
  }
}

ReenTrantReadWriteLock鎖可以被用於一些集合型別來提高併發效能。

當集合預計很大,而且訪問的讀執行緒明顯多於寫執行緒時,使用ReenTrantReadWriteLock鎖是值得的。

例如,下面是一個類,這個類使用TreeMap,這個TreeMap預計會很大並且被併發訪問:

class RWDictionary {
  private final Map<String, Data> m = new TreeMap<String, Data>();
  private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
  private final Lock r = rwl.readLock();
  private final Lock w = rwl.writeLock();

  public Data get(String key) {
    r.lock();
    try { return m.get(key); }
    finally { r.unlock(); }
  }
  public String[] allKeys() {
    r.lock();
    try { return m.keySet().toArray(); }
    finally { r.unlock(); }
  }
  public Data put(String key, Data value) {
    w.lock();
    try { return m.put(key, value); }
    finally { w.unlock(); }
  }
  public void clear() {
    w.lock();
    try { m.clear(); }
    finally { w.unlock(); }
  }
}

實現注意事項:

此鎖支援最多65535個遞迴寫鎖65535個讀鎖
超過這些限制的將導致鎖定方法報錯。

3.例項

例項場景:

本次需要編碼的例項場景很簡單,驗證讀鎖寫鎖的互斥關係:

  • 互斥
  • 互斥
  • 互斥
  • 共享

直接上程式碼:

/**
 * <p>Lock介面-讀寫鎖</p>
 *
 * @author hanchao 2018/3/18 15:40
 **/
public class ReadWriteLockDemo {
    //定義非公平的讀寫鎖
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(false);

    /**
     * <p>lock介面-讀寫鎖</p>
     *
     * @author hanchao 2018/3/18 15:41
     **/
    public static void main(String[] args) throws InterruptedException {
        /**
         * 0 寫寫互斥
         * 1 寫讀互斥
         * 2 讀寫互斥
         * 3 讀讀共享
         */
        int type = 3;
        switch (type) {
            case 0://寫寫互斥
                //共用同一個lock物件的寫鎖
                Lock writeLock = lock.writeLock();
                //寫
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取寫鎖...");
                    writeLock.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了寫鎖.");
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        writeLock.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了寫鎖.");
                    }
                }).start();
                //寫
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取寫鎖...");
                    writeLock.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了寫鎖.");
                    try {
                        Thread.sleep(1800);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        writeLock.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了寫鎖.");
                    }
                }).start();
                break;
            case 1://寫讀互斥
                //共用同一個lock物件的寫鎖、讀鎖
                Lock writeLock1 = lock.writeLock();
                Lock readLock1 = lock.readLock();
                //寫
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取寫鎖...");
                    writeLock1.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了寫鎖.");
                    try {
                        Thread.sleep(1900);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        writeLock1.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了寫鎖.");
                    }
                }).start();
                //讀
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取讀鎖...");
                    readLock1.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了讀鎖.");
                    try {
                        Thread.sleep(2100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        readLock1.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了讀鎖.");
                    }
                }).start();
                break;
            case 2://讀寫互斥
                //共用同一個lock物件的寫鎖、讀鎖
                Lock writeLock2 = lock.writeLock();
                Lock readLock2 = lock.readLock();
                //讀
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取讀鎖...");
                    readLock2.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了讀鎖.");
                    try {
                        Thread.sleep(2200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        readLock2.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了讀鎖.");
                    }
                }).start();
                //寫
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取寫鎖...");
                    writeLock2.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了寫鎖.");
                    try {
                        Thread.sleep(1700);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        writeLock2.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了寫鎖.");
                    }
                }).start();
                break;
            case 3://讀讀共享
                //共用同一個lock物件的讀鎖
                Lock readLock3 = lock.readLock();
                //讀
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取讀鎖...");
                    readLock3.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了讀鎖.");
                    try {
                        Thread.sleep(1800);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        readLock3.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了讀鎖.");
                    }
                }).start();
                //讀
                new Thread(() -> {
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]嘗試獲取讀鎖...");
                    readLock3.lock();
                    System.out.println("執行緒[" + Thread.currentThread().getName() + "]獲取了讀鎖.");
                    try {
                        Thread.sleep(1600);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } finally {//在finally程式碼塊中是否鎖
                        readLock3.unlock();
                        System.out.println("執行緒[" + Thread.currentThread().getName() + "]釋放了讀鎖.");
                    }
                }).start();
                break;
            default:
                break;
        }
        Thread.sleep(500);
        System.out.println("============================");
    }
}

測試結果:

寫寫互斥:

執行緒[Thread-0]嘗試獲取寫鎖...
執行緒[Thread-0]獲取了寫鎖.
執行緒[Thread-1]嘗試獲取寫鎖...
============================
執行緒[Thread-0]釋放了寫鎖.
執行緒[Thread-1]獲取了寫鎖.
執行緒[Thread-1]釋放了寫鎖.

寫讀互斥:

執行緒[Thread-0]嘗試獲取寫鎖...
執行緒[Thread-1]嘗試獲取讀鎖...
執行緒[Thread-0]獲取了寫鎖.
============================
執行緒[Thread-0]釋放了寫鎖.
執行緒[Thread-1]獲取了讀鎖.
執行緒[Thread-1]釋放了讀鎖.

讀寫互斥:

執行緒[Thread-0]嘗試獲取讀鎖...
執行緒[Thread-1]嘗試獲取寫鎖...
執行緒[Thread-0]獲取了讀鎖.
============================
執行緒[Thread-0]釋放了讀鎖.
執行緒[Thread-1]獲取了寫鎖.
執行緒[Thread-1]釋放了寫鎖.

讀讀共享:

執行緒[Thread-0]嘗試獲取讀鎖...
執行緒[Thread-0]獲取了讀鎖.
執行緒[Thread-1]嘗試獲取讀鎖...
執行緒[Thread-1]獲取了讀鎖.
============================
執行緒[Thread-1]釋放了讀鎖.
執行緒[Thread-0]釋放了讀鎖.

4.總結

主要對ReentrantReadWriteLock類進行總結:

1.訪問順序

支援可選的公平策略,預設為非公平模式

2.可重入性

  • ReentrantLock類似。
  • 寫執行緒可以獲取讀鎖讀執行緒不可以獲取寫鎖

3.可降級性

  • 允許寫鎖降級成為讀鎖:首先獲取寫鎖,然後獲取讀鎖,然後釋放寫鎖
  • 不允許從讀鎖升級為寫鎖

4.可中斷性

讀鎖寫鎖都可以被中斷。

5.支援Condition

只有寫鎖支援。

6.監測方法

ReentrantReadWriteLock類提供了一些方法用於檢視鎖的狀態,而非同步控制。

7.互斥規則

  • 互斥
  • 互斥
  • 互斥
  • 共享