JUC之ReadWriteLock

◇◆丶佛笑我妖孽 提交于 2019-11-26 14:31:42

JUC之读写锁解读

一、什么是读写锁

​ 读写锁实现的功能就是“读写分离”,读可以并发读,写只能串行写,同时,读的时候不能写,写的时候不能读。但是,如何控制读与写,需要我们手动在读代码块上加读锁,写代码上加写锁。

二、读写锁的实现

​ 这里我主要讲一些内部实现原理。

  • 读为共享锁
final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (; ; ) {
        int c = getState();
      //判断是否此时有写锁。
        if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
            return false;
      //读锁的共享数量
        int r = sharedCount(c);
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
      //尝试CAS
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null ||
                        rh.tid != LockSupport.getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return true;
        }
    }
}
  • 写为独占锁
final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
  //获取状态
    int c = getState();
  //c!=0表示有线程持有锁;
    if (c != 0) {
      //获取独占锁(写锁)的数量;
        int w = exclusiveCount(c);
      //w=0,证明写锁为0,当前是读锁状态,写无法获取锁。
      //w!=0&当前线程不是独占线程,那么,证明是其它线程获取写锁,不能共用。
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
  //正常情况,尝试CAS。
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
  • 锁的计数器
    //TODO 写线程持有的重入数
    public int getWriteHoldCount() {
        return sync.getWriteHoldCount();
    }
    //TODO 每个读线程持有的重入数
    public int getReadHoldCount() {
        return sync.getReadHoldCount();
    }
  • 内部结构
    • Syn实现了大部分的操作
    • ReadLock、WriteLock封装了Syn中的操作
    • FairSyn与UnfairSyn分别有自己的部分实现

三、升降级

1、锁降级

​ ReentrantReadWriteLock支持锁降级,即从写锁降级变为读锁,但是使用过程仍然要注意:

class CachedData {
  Object data;
  volatile boolean cacheValid;
  final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

  public 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;
        }
        // 在释放写锁之前通过获取读锁降级写锁(注意此时还没有释放写锁)
        //在写锁没有释放的时候,先申请读锁,成功了再释放写锁,这个过程就称为锁降级。
        rwl.readLock().lock();
      } finally {
        rwl.writeLock().unlock(); // 释放写锁而此时已经持有读锁
      }
    }

    try {
      use(data);
    } finally {
      rwl.readLock().unlock();
    }
  }
}

锁降级的核心:先把持住当前的写锁,再获取读锁,最后释放写锁。如果是先释放读写,再获取写锁,那个过程并不是锁降级。

2、锁升级

​ ReentrantReadWriteLock是不支持锁升级的。

四、使用场景

  • 分布式缓存中间件
    • 写的时候需要阻塞
    • 读的时候可以并发
  • 维护信道channel
    • 维护TCP链接的时候可以采用读写锁

使用读写锁的场景最好还是那种读频繁,写稀疏的场景。

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!