美文网首页
读写锁ReentrantReadWriteLock(gold_a

读写锁ReentrantReadWriteLock(gold_a

作者: 胖达_4b7e | 来源:发表于2020-02-06 10:10 被阅读0次

https://book.douban.com/subject/27034721/

存在的原因

锁的排他性, 让线程不能同时得到锁, 但是

synchronized,ReentrantLock,无论是读还是写,它们都要求获得相同的锁(排他性)。
在一些场景中,这是没有必要的,多个线程的读操作完全可以并行,在读多写少的场景中,让读操作并行可以明显提高性能。

适用

读多写少, 读的时间长

不然一般不用, 开销大

定义

就是 通过一个ReadWriteLock产生两个锁,
一个读锁,一个写锁。只有"读-读"操作是可以并行的.

ReadWriteLock

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

这个接口是读写锁的抽象,
里面有 readLock() writeLock() 这2方法, 得到读锁写锁,

返回的锁是Lock类型的(这是锁接口,ReentrantLock也实现它)

使用例子:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockUsage {
  private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
  private final Lock readLock = rwLock.readLock();
  private final Lock writeLock = rwLock.writeLock();

  // 读线程执行该方法
  public void reader() {
    readLock.lock(); // 申请读锁
    try {
      // 在此区域读取共享变量
    } finally {
      readLock.unlock();// 总是在finally块中释放锁,以免锁泄漏
    }
  }

  // 写线程执行该方法
  public void writer() {
    writeLock.lock(); // 申请读锁
    try {
      // 在此区域访问(读、写)共享变量
    } finally {
      writeLock.unlock();// 总是在finally块中释放锁,以免锁泄漏
    }
  }
}
  • ReentrantReadWriteLockReadWriteLock的默认实现
  • ReadWriteLock 可得到的类型是 Lock, 看起来就是普通的锁, 是不是实现了读写锁的功能, 看实现者的良心
  • 读写锁都是普通Lock, 没啥特别的,和ReentrantLock 一样用就行了

锁的降级Downgrade

就是 有在得写锁的时候, 可继续得读锁,
写锁锁住, 不释放, 在其临界区内得读锁

这样严格排他的写锁,就可以降级为可共享的读锁了

ReentrantReadWriteLock这个 读写锁的默认实现, 是个可重入锁

例子:

    writeLock.lock(); // 申请写锁
    try {
      // 对共享数据进行更新
      // ...
      // 当前线程在持有写锁的情况下申请读锁readLock
      readLock.lock();
    } finally {
      writeLock.unlock();// 释放写锁
    }

为什么只能降级不能升级:

内部实现 先得读锁 再得写锁 会死锁

为什么要这么设计?
比如 同一段代码写着
readLock.lock();
writeLock.unlock();
t1,t2,t3 线程都先拿到了读锁, t1去拿写锁要等t2 t3先释放读锁, 他们也在等t1 就死锁了

使用例子: 实现一个缓存类MyCache

public class MyCache {

    //数据存这
    private Map<String, Object> map = new HashMap<>();
    
    private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private Lock readLock = readWriteLock.readLock();
    private Lock writeLock = readWriteLock.writeLock();

    //读要得读锁
    public Object get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    //写要写锁
    public Object put(String key, Object value) {
        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            writeLock.unlock();
        }
    }

    public void clear() {
        writeLock.lock();
        try {
            map.clear();
        } finally {
            writeLock.unlock();
        }
    }

}

https://mp.weixin.qq.com/s/jTRpP66tL8l49XRUWDtnYQ

唤醒注意

内部,
它们使用同一个整数变量表示锁的状态,16位给读锁用,16位给写锁用,使用一个变量便于进行CAS操作,

锁的等待队列其实也只有一个。

  • 都是获取不到锁就入队,
  • 释放后, 将等待队列中的第一个线程唤醒,唤醒的可能是等待读锁的,也可能是等待写锁的

读锁获取

只要写锁没有被持有,就可以获取到
获取后,它会检查等待队列,逐个唤醒最前面的等待读锁的线程,直到第一个等待写锁的线程。

写锁 加锁源码

获取 写锁



1.如果是本来有锁了, 只能是本来有写锁, 就是我 , 我重入
2.是不是需要排队? 这个子类实现 公平非公平锁 不同, 公平锁要前面没等的了才行
3.产生cas拿锁, 如果成功 把本线程记一下

注意: 这里int w = exclusiveCount(c); AQS同一个state状态 ReentrantReadWriteLock 分成2部分 又标记写锁又标记读锁,
这里是解析出读锁, 这里倒是自己实现 , 不是用的AQS

写锁 解锁 源码


unparkSuccessor ↑是AQS方法 , 和 ReentrantLock 是一样的

模板方法子类实现↓


可以看到写锁 逻辑 和ReentrantLock 的几乎是一样的

读锁 加锁(共享锁 很复杂)

复杂是因为这是共享锁

tryAcquireShared获取锁

总览 tryAcquireShared

这里还和独占锁差不多↑
除了 有读锁了 还是可以降级
和独占锁一样的是 前面没有, 没到重数上限就可以cas试一下

加锁成功后, 比较复杂的是↓ 每个线程都要ThreadLocak各自记下来 加了几重读锁了, first的读锁线程 另外记 因为经常用到


tryAcquireShared

获取锁失败后doAcquireShared 排队

读锁释放

会把排队的叫醒


叫醒直到遇到读锁

确定 和StampedLock 对比

  • 非公平锁的情况下, 读多写少, 写的可能永远钱不到锁,永远在排队, 饿死 ,StampedLock 不会 它有乐观读
  • 效率低, 读写互斥, StampedLock 不会, 乐观读的同时可以写

相关文章

网友评论

      本文标题:读写锁ReentrantReadWriteLock(gold_a

      本文链接:https://www.haomeiwen.com/subject/ftsyxhtx.html