学习笔记:读写锁ReentrantReadWriteLock的原理

读写锁ReentrantReadWriteLock的原理

ReentrantReadWriteLock采用读写分离的策略,允许多个线程可以同时获取读锁。
在这里插入图片描述
在这里插入图片描述

读写锁的内部维护了一个ReadLock和一个WriteLock,它们依赖Sync实现具体功能,而Sync继承自AQS,并且也提供了公平和非公平的实现。

ReentrantReadWriteLock使用state的高16位表示读状态,也就是获取到读锁的次数;使用低16位表示获取到写锁的线程的可重入次数。

 static final int SHARED_SHIFT   = 16;
 static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
 static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

//返回读锁线程数
 static int sharedCount(int c)  { return c >>> SHARED_SHIFT; }
  
  //返回写锁可重入个数   
 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
写锁的获取与释放

WriteLock

1、void lock()
public void lock() {
    sync.acquire(1);
 }
  • 写锁是个独占锁,某时只有一个线程可以获取该锁
  • 如果当前没有线程获取到读锁和写锁,则当前线程可以获取到写锁然后返回
  • 如果当前已有线程获取到读锁和写锁,则当前请求写锁的线程会被阻塞挂起
  • 写锁是可重入锁
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
    // (Note: if c != 0 and w == 0 then shared count != 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、如果当前AQS状态值不为0,则说明当前已经有线程获取到了读锁或写锁
2、w==0 说明状态值的低16位为0,而AQS的状态值不为0,则说明高16位不为0,已经有线程获取了读锁 return false;
3、w!=0,说明已经有线程获取了写锁,检查当前线程是不是该锁的持有者,不是: return false;
4、判断可重入次数是否超过了最大值w + exclusiveCount(acquires) > MAX_COUNT,如果超过了,throw new Error("Maximum lock count exceeded");否则增加当前线程的可重入次数,然后return true;
5、如果当前AQS状态值为0,则说明目前没有线程获取到读锁和写锁,会执行writerShouldBlock()
对于writerShouldBlock(),非公平锁的实现为:

final boolean writerShouldBlock() {
    return false; // writers can always barge
}

对于非公平锁来说总是返回false,然后通过compareAndSetState(c, c + acquires)抢占式执行CAS尝试获取写锁,成功则设置当前锁的持有者位当前线程并返回true,否则返回false。

对于公平锁的实现:

  final boolean writerShouldBlock() {
      return hasQueuedPredecessors();
  }    

使用hasQueuedPredecessors()来判断

2、void lockInterruptibly()
 public void lockInterruptibly() throws InterruptedException {
       sync.acquireInterruptibly(1);
 }

类似于lock()方法,不同之处在于,它会对中断进行响应,也就是当其他线程调用了interrupt()方法中断了当前线程时,当前线程会抛出InterruptedException

3、boolean tryLock( )
 public boolean tryLock( ) {
    return sync.tryWriteLock();
 }

尝试获取写锁,
1、如果当前没有其他线程持有写锁或者读锁,则当前线程会获取写锁成功,return true;
2、如果当前已经有其他线程持有写锁或者读锁,return false;且当前线程并不会被阻塞。
3、如果当前线程已经持有了该写锁则简单增加AQS的状态值会直接return true;

 final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {
         int w = exclusiveCount(c);
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         if (w == MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
     }
     
     if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
 }
4、void unlock()
public void unlock() {
   sync.release(1);
}

尝试释放锁:
1、如果当前线程持有该锁,调用该方法会让该线程持有的AQS状态值减1,减1后状态值为0则释放该锁,否则仅仅是减1而已
2、如果当前线程没有持有该锁,调用该方法会抛出IllegalMonitorStateException

读锁的获取与释放

ReadLock

1、void lock()

获取读锁,
1、如果当前没有其他线程持有写锁,则当前线程可以获取读锁,AQS的状态值state的高16位的值会增加1,然后方法返回。
2、如果其他一个线程持有写锁,则当前线程会被阻塞。

 public void lock() {
    sync.acquireShared(1);
 }

读锁的lock()方法调用了AQS的acquireShared方法,在其内部调用了ReentrantReadWriteLocksync重写的tryAcquireShared方法。

public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
      doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
    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);
}
2、void lockInterruptibly()

类似于lock()方法,不同之处在于,它会对中断进行响应,也就是当其他线程调用了interrupt()方法中断了当前线程时,当前线程会抛出InterruptedException

3、boolean tryLock( )

尝试获取写锁,
1、如果当前没有其他线程持有写锁,则当前线程会获取读锁成功,return true;
2、如果当前已经有其他线程持有写锁,return false;且当前线程并不会被阻塞。
3、如果当前线程已经持有了该读锁则简单增加AQS的状态值高16位后会直接return true;

4、void unlock()
 public void unlock() {
     sync.releaseShared(1);
 }

具体释放锁的操作是委托给Sync来做的。

在这里插入图片描述

小结:

  • ReentrantReadWriteLock底层是使用AQS实现的
  • ReentrantReadWriteLock使用AQS的状态值高16为表示获取到读锁的个数,低16位表示获取写锁的线程的可重入次数,并通过CAS对其进行操作实现了读写分离
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值