并发编程篇:读写锁(ReentrantReadWriteLock)的源码分析

x33g5p2x  于2021-12-18 转载在 其他  
字(3.0k)|赞(0)|评价(0)|浏览(236)

定义

1、读写锁在同一时刻有多个读线程访问,在写线程时候所有的读锁和其他写线程都被阻塞。读写锁维护了一个读锁和一个写锁,通过分离读锁和写锁,并发性比其他排它锁都提升了。它具备3个特性:

  • 公平性选择:支持非公平(默认)和公平获取的锁获取方式,吞吐量还是非公平优于公平
  • 重进入:该锁支持重进入;读线程为例,获取了该锁之后,能够再次获取读锁。而写线程在获取写锁之后能够再次获取写锁,同时也可以获取读锁
  • 锁降级:遵循获取写锁】获取读锁再释放写锁的次序,写锁能够降级成为读锁。

读写锁的实现分析

1、读写锁的状态设计

使用一个整形编码维护读写状态,按位切割使用这个变量,其中高16位表示读,低16位表示写,如下图

1、获取状态:如果当前同步状态是S,写状态为 S & 0x0000FFFF(将高16位全部抹去),读状态为S >>> 16(无符号补0右移16位)
2、当写状态增加1是,等于S+1;读状态增加1是,等于S+(1<< 16)
3、当S不等于0时,当写状态(S & 0xooooFFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取

写锁的获取与释放

写锁是支持重入的排它锁。如果当前线程已经获取了写锁,则增加写锁的状态。如果当前线程在获取读锁是,读锁已被获取或者该线程不是已经获取写锁的线程,则当前线程进入等待状态。

protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            //获取锁的状态
            int c = getState();
            //获取写锁的状态
            int w = exclusiveCount(c);
            if (c != 0) {
                // 存在读锁或者当前线程不是已获取读写锁的线程
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                setState(c + acquires);
                return true;
            }
            if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

获取写锁的公平和非公平如何保证

读锁的获取与释放

1、读锁是一个支持重进入的共享锁,能够被多个线程同时获取,在没有其他写线程访问时候,读锁总是可以被重新获取的。
2、当前线程重新获取读锁,则增加读的状态。
3、如果当前线程在获取读锁时候,其他线程获取了写锁,则进入等待状态

*/
            Thread current = Thread.currentThread();
            int c = getState();
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                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 != getThreadId(current))
                        cachedHoldCounter = rh = readHolds.get();
                    else if (rh.count == 0)
                        readHolds.set(rh);
                    rh.count++;
                }
                return 1;
            }
            return fullTryAcquireShared(current);
        }
公平获取锁和非公平获取锁

公平锁看是否有前驱节点

static final class FairSync extends Sync {
        private static final long serialVersionUID = -2274990926593161451L;
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }

非公平锁,直接返回false

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            return apparentlyFirstQueuedIsExclusive();
        }
    }

锁降级

锁的降级,是指把持住(当前拥有的)写锁,再获取到读锁,随后释放(先前拥有的)写锁的过程

//读写锁
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    //读锁
    private ReadLock readLock=lock.readLock();
    //写锁
    private WriteLock writeLock=lock.writeLock();
    private boolean update;

  public void processData(){
      //读锁获取
      readLock.lock();

      if(!update){
          //必须先释放读锁
          readLock.unlock();

          //锁降级从获取写锁开始
          writeLock.lock();
          try {
                  if(!update){
                      //准备数据流程(略)
                      update=true;
                  }
                  //获取读锁。在写锁持有期间获取读锁
                  //此处获取读锁,是为了防止,当释放写锁后,又有一个线程T获取锁,对数据进行改变,而当前线程下面对改变的数据无法感知。
                  //如果获取了读锁,则线程T则被阻塞,直到当前线程释放了读锁,那个T线程才有可能获取写锁。
                  readLock.lock();
              }finally{
                  //释放写锁
                  writeLock.unlock();
              }
          //锁降级完成
      }

      try {
          //使用数据的流程
      } finally{
          //释放读锁
          readLock.unlock();
      }

  }

锁降级的必要性:主要为了保证数据的可见性。如果当前线程不获取读锁而是直接释放写锁,假设此刻另一个线程(T)获取了写锁并修改了数据,那么当前线程无法感知线程T的数据更新。如果当前线程获取读锁,即遵循锁的降级步骤,则线程T将会被阻塞,直到线程使用数据并释放读锁之后,线程T才能获取读写锁更新数据。

相关文章