zoukankan      html  css  js  c++  java
  • 读写锁之ReadWriteLock

    你可能有这样一个疑问,Java SDK 并发包里为什么还有很多其他的工具类呢?原因很简单:分场景优化性能,提升易用性

    针对读多写少这种并发场景,Java SDK 并发包提供了读写锁——ReadWriteLock
    读写锁,并不是 Java 语言特有的,而是一个广为使用的通用技术,所有的读写锁都遵守以下三条,尤其注意第三条,之后我们讲跟其他锁的对比会用到。
    基本原则:
    1. 允许多个线程同时读共享变量;
    2.只允许一个线程写共享变量;
    3. 如果一个写线程正在执行写操作,此时禁止读线程读共享变量(读锁 写锁是互斥的,不能同时存在)
     

    互斥锁:

     
     

    互斥锁

    互斥锁

    升降级

    ReadWriteLock

    读操作允许多个线程同时读共享变量

    写操作是互斥的,当一个

    线程在写共享变量的时候,是不允许其他线程执行写操作和读操作。

    不允许升级,允许降级。

    读锁不支持条件变量newCondition()

     
     
     
    一:用 ReadWriteLock 快速实现一个通用的缓存工具类
     1 class MyCache<K,V> {
     2     
     3     final Map<K, V> m = new HashMap<>();
     4     final ReadWriteLock rwl = new ReentrantReadWriteLock();
     5     // 读锁 
     6     final Lock readLock = rwl.readLock();
     7     
     8     // 写锁 
     9     final Lock writeLock = rwl.writeLock();
    10     // 读缓存
    11     V get(K key) {
    12         readLock.lock();
    13         try { return m.get(key); }
    14         finally { 
    15             readLock.unlock(); 
    16         }
    17     }
    18     
    19     // 写缓存
    20     V put(String key, Data v) {
    21         writeLock.lock();
    22         try { return m.put(key, v); }
    23         finally { 
    24             writeLock.unlock();
    25         }
    26     }
    27 }
    View Code
    面的这段代码实现了按需加载的功能,这里我们假设缓存的源头是数据库。先从缓存中获取数据,若缓存中没有,就从数据库中加载,然后写入缓存。
    经验之谈:在获取写锁之后,并不直接查数据库,而是再次验证缓存是否存在,为什么呢?
     1 class MyCache<K,V> {
     2 final Map<K, V> m =  new HashMap<>();
     3 final ReadWriteLock rwl =  new ReentrantReadWriteLock();
     4 final Lock r = rwl.readLock();
     5 final Lock w = rwl.writeLock();
     6  V get(K key) {
     7         V v = null;
     8           // 读缓存
     9           r.lock(); ①
    10           try {
    11              v = m.get(key); ②
    12              } finally{
    13                  r.unlock(); ③
    14              }
    15           // 缓存中存在,返回
    16           if(v != null) { ④
    17               return v;
    18               }
    19           // 缓存中不存在,查询数据库
    20           w.lock(); ⑤
    21           try {
    22               // 获取到写锁,再次验证 
    23               v = m.get(key); ⑥
    24               if(v == null){ ⑦
    25                   //假设A1线程获取读锁,并更新了数据库,释放了读锁
    26                   // 接下来A2线程得到读锁,获取读锁,并更新了数据库,释放了读锁
    27                   // A3线程也是如此操作,
    28                   // 获取到读锁再次验证是否存在,就能避免后续线程多次操作数据库
    29                   // 查询数据库 代码省略
    30                   //....................
    31                   m.put(key, v);
    32                   }
    33               } finally{
    34               w.unlock();
    35               }
    36           return v;
    37           }
    38  }
    View Code
    二:锁的升级,在获取读锁后,发现是空的,(没有释放读锁),继续获得写锁,执行后续操作。实际上这种操作是不允许的,因为读锁和写锁互斥
    读锁还没有释放,此时获取写锁,会导致写锁永久等待,最终导致相关线程都被阻塞,永远也没有机会被唤醒。锁的升级是不允许的,这个你一定要注意。 
     
    // 读缓存 2 
    r.lock(); ①
    try { v = m.get(key); ② if (v == null) { w.lock(); try { // 再次验证并更新缓存 9 // 省略详细代码 } finally{ w.unlock(); } } } finally{ r.unlock(); ③ }

    三:锁的降级

    不过,虽然锁的升级是不允许的,但是锁的降级却是允许的。以下代码来源自ReentrantReadWriteLock 的官方示例,伪代码如下
     1 class CachedData {
     2     Object data;
     3     volatile boolean cacheValid;
     4     final ReadWriteLock rwl = new ReentrantReadWriteLock();
     5     // 读锁
     6     final Lock r = rwl.readLock();
     7     // 写锁
     8     final Lock w = rwl.writeLock();
     9 
    10     void processCachedData() {
    11         // 获取读锁
    12         r.lock();
    13         if (!cacheValid) {
    14             // 释放读锁,因为不允许读锁的升级
    15             r.unlock();
    16             // 获取写锁
    17             w.lock();
    18             try {
    19                 // 再次检查状态
    20                 if (!cacheValid) {
    21                     data = ...
    22                     cacheValid = true;
    23                 }
    24                 // 释放写锁前,降级为读锁
    25                 // 降级是可以的
    26                 r.lock(); ①
    27             } finally {
    28                 // 释放写锁
    29                 w.unlock();
    30             }
    31         }
    32         // 此处仍然持有读锁
    33         try {use(data);}
    34         finally {r.unlock();}
    35     }
    36 }
     
     
    总结:
    读写锁类似于 ReentrantLock,也支持公平模式和非公平模式。读锁和写锁都实现了java.util.concurrent.locks.Lock 接口,所以除了支持 lock() 方法外,tryLock()、lockInterruptibly() 等方法也都是支持的。但是有一点需要注意,那就是只有写锁支持条件变量,读锁是不支持条件变量的,读锁调用 newCondition() 会抛出 UnsupportedOperationException异常
     
    文中部分代码引用:王宝令  ReadWriteLock:如何快速实现一个完备的缓存
     
     
     
    ==========================================================================           如果您觉得这篇文章对你有帮助,可以【关注我】或者【点赞】,希望我们一起在架构的路上,并肩齐行
    ==========================================================================
  • 相关阅读:
    第二次结对编程作业
    第5组 团队展示
    第一次结对编程作业
    BETA 版冲刺前准备(团队)
    项目测评(团队)
    1111111111
    Alpha 事后诸葛亮
    Alpha 冲刺 (10/10)
    Alpha 冲刺 (9/10)
    Alpha 冲刺 (8/10)
  • 原文地址:https://www.cnblogs.com/amberJava/p/12355474.html
Copyright © 2011-2022 走看看