深入浅出多线程编程实战读写锁ReentrantReadWriteLock

Posted 、Dong

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入浅出多线程编程实战读写锁ReentrantReadWriteLock相关的知识,希望对你有一定的参考价值。


前言

在前面的文章中我们介绍过两种锁:内置锁(synchronized)和显式锁(ReentrantLock)。这两种锁都是独占锁,也就是说获取到这个锁之后其它线程再想获取这个锁必须等当前线程释放这个锁。有些时候线程多数情况下都是读取数据的值,而不是修改这个数据,但是读取数据的线程之间是可以并发读取的,如果使用独占锁会限制系统的性能,由此读写锁应运而生。


一、ReadWriteLock

ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
所有读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。
读写锁比互斥锁允许对于共享数据更大程度的并发。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。
Java并发包中ReadWriteLock是一个接口,主要有两个方法,如下:

public interface ReadWriteLock {
    /**
     * 返回读锁
     */
    Lock readLock();

    /**
     * 返回写锁
     */
    Lock writeLock();
}

Java并发库中ReetrantReadWriteLock实现了ReadWriteLock接口并添加了可重入的特性。


二、ReentrantReadWriteLock

1.特性

  • 公平性

    • 非公平锁(默认):这个和独占锁的非公平性一样,由于读线程之间没有锁竞争,所以读操作没有公平性和非公平性,写操作时,主张竞争获取,可能会延缓一个或多个读或写操作,但是会比公平锁有更高的吞吐量。
    • 公平锁:当以公平模式初始化时,线程将会以队列的顺序获取锁。当当前线程释放锁后,等待时间最长的写锁线程就会被分配写锁;或者有一组读线程组等待时间比写线程长,那么这组读线程组将会被分配读锁。
      当有写线程持有写锁或者有等待的写线程时,一个尝试获取公平的读锁(非重入)的线程就会阻塞。这个线程直到等待时间最长的写锁获得锁后并释放掉锁后才能获取到读锁。
  • 可重入性

    • 读写锁允许读线程和写线程按照请求锁的顺序重新获取读取锁或者写入锁。当然了只有写线程释放了锁,读线程才能获取重入锁。
    • 写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁。
    • 另外读写锁最多支持65535个递归写入锁和65535个递归读取锁。
  • 锁降级:因为当线程持有写锁的时候没有其它线程可以获取读写锁,因此再获取读锁是安全的。而此时再释放写锁就会降级为读锁,其它线程也可以获得读锁。

  • 锁升级:不允许锁升级,因为这会导致死锁,如果只有一个线程试图升级锁是没有问题的,可是只有一个线程毕竟是少数情况。试想如果两个线程同时尝试升级锁,两个线程都在等待对方释放读锁,它们之间就发生了死锁。

  • 锁获取中断:读取锁和写入锁都支持获取锁期间被中断。这个和独占锁一致。

2.使用样例

ReentrantReadWriteLock可以用来提高某些集合的并发性能。当集合比较大,并且读比写频繁时,可以使用该类。下面是TreeMap使用ReentrantReadWriteLock进行封装成并发性能提高的一个例子:

public 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(); }
   }
 }


结尾

  • 感谢大家的耐心阅读,如有建议请私信或评论留言。
  • 如有收获,劳烦支持,关注、点赞、评论、收藏均可,博主会经常更新,与大家共同进步

以上是关于深入浅出多线程编程实战读写锁ReentrantReadWriteLock的主要内容,如果未能解决你的问题,请参考以下文章

实战并发编程 - 01多线程读写同一共享变量的线程安全问题深入剖析

多线程编程之读写锁

带你深入理解多线程 --- 锁策略篇

带你深入理解多线程 --- 锁策略篇

Linux:详解多线程(线程池读写锁和CAS无锁编程)

多线程编程之Apue3rd_Chapter11之互斥锁_读写锁_自旋锁