jdk1.8 ReentrantReadWriteLock源码分析

ReentrantReadWriteLock

ReentrantReadWriteLock是可重入的读写锁,下面我们将通过源码来分析它是怎么工作的

重要属性

/** Inner class providing readlock */
 private final ReentrantReadWriteLock.ReadLock readerLock;
 /** Inner class providing writelock */
 private final ReentrantReadWriteLock.WriteLock writerLock;
 /** Performs all synchronization mechanics */
 final Sync sync;

ReentrantLockReadWriteLock一共有下面几个重要的属性:
(1)和其他AQS组件一样,有一个Sync对象,用来进行底层的并发控制
(2)ReadLock是ReentrantLockReadWriteLock的一个内部类,代表一个读锁
(3)WriteLock是ReentrantLockReadWriteLock的一个内部类,代表一个写锁

构造函数

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

构造函数接收一个boolean类型的参数,用来代表是否实现公平锁,关于公平和不公平是如何实现的可以看之前的博客

ReadLock

重要属性

private final Sync sync;

构造函数

protected ReadLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
}

ReadLock的构造函数传入的是ReentrantLockReadWriteLock对象,使用该对象的并发控制器作为自己的并发控制器

其他重要函数

public void lock() {
    sync.acquireShared(1);
}
public boolean tryLock() {
    return sync.tryReadLock();
}
public Condition newCondition() {
    throw new UnsupportedOperationException();
}
public void unlock() {
   sync.releaseShared(1);
}

因为lock()调用的是acquireShared(),所以ReadLock是一个共享式的锁

WriteLock

重要属性

private final Sync sync;

构造函数

protected WriteLock(ReentrantReadWriteLock lock) {
    sync = lock.sync;
}
```
WriteLock的构造函数和ReadLock的类似
### 其他重要函数
```java
public void lock() {
    sync.acquire(1);
}
public Condition newCondition() {
    return sync.newCondition();
}
//  thread has a hold on a lock for each lock action that is not matched by an unlock action.
public int getHoldCount() {
    return sync.getWriteHoldCount();
}
// 判断锁是否被当前线程排他地占用
public boolean isHeldByCurrentThread() {
    return sync.isHeldExclusively();
}
```
lock()调用的是acquire(),所以WriteLock是排它锁

## Sync
下面看一下Sync的具体实现
### 重要属性
```java
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;
// 第一个读者线程
private transient Thread firstReader = null;
// 第一个读者重入的次数
private transient int firstReaderHoldCount;
// 最近的读者的重入次数
private transient HoldCounter cachedHoldCounter;
/** Returns the number of shared holds represented in count  */
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
/** Returns the number of exclusive holds represented in count  */
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
```

ReentrantLock将一个32位的int类型的变量state分成了两个部分,两个部分都分别占用16位,高16位用来存储读锁的状态,低16位用来存储写锁的状态
![在这里插入图片描述](https://img-blog.csdnimg.cn/20190331203441219.png)

另外这里会记录每个读线程的重入次数,如果是第一个读者,那么它的读重入次数就设置在了ReentrantReadWriteLock中,如果是最近的读者,那么也会缓存到ReentrantReadWriteLock中
其他读者会使用ThreadLocal设置到各个线程中
### 对应ReadLock的共享式操作
#### 获取
```java
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);
   // 首先执行readerShouldBlock,该方法时一个抽象方法,根据ReentrantLock是公平还是不公平的而实现不同
   // 接着判断当前读的是否小于读的上限
   // 接着使用cas竞争资源,增加读的数量,其实就是向高16位单独看成一个整数,然后加1
   // 如果是公平模式,队里中有元素的话,readerShouldBlock会返回true
  	// 条件返回true的情况:不应该阻塞读者,当前读者的数量没有超过最大值,成功更新了资源量
   if (!readerShouldBlock() &&
       r < MAX_COUNT &&
       compareAndSetState(c, c + SHARED_UNIT)) {
       // 如果当前线程是第一个读者
       // 设置当前线程是第一个读者
       // 并且更新第一个读者的重入次数
       if (r == 0) {
       		// 当前线程是第一个读者
           firstReader = current;
           firstReaderHoldCount = 1;
       } else if (firstReader == current) {
       		// 当前线程是第一个读者,但是是重入的
           firstReaderHoldCount++;
       } else {
       		// 当前线程不是第一个读者
       		// 使用cachedHoldCounter代表最近的读者的重入次数
       		// 这里使用cachedHoldCounter的原因是,可能一个线程一直进行重入读,或者释放读锁,那么使用缓存可以加快处理速度
           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;
   }
   // 可能是读锁应该被阻塞,读锁数量超过上限,也可能是使用cas更新状态失败,下面使用循环加cas的方法来尝试设置
   return fullTryAcquireShared(current);
}
```

```java
final int fullTryAcquireShared(Thread current) {
   /*
    * This code is in part redundant with that in
    * tryAcquireShared but is simpler overall by not
    * complicating tryAcquireShared with interactions between
    * retries and lazily reading hold counts.
    */
   HoldCounter rh = null;
   for (;;) {
   		// 获取状态
       int c = getState();
       // 当前有线程在写,并且那个占用写锁的线程不是当前线程,那么结束循环,返回-1
       // 代表不能获取读锁
       if (exclusiveCount(c) != 0) {
           if (getExclusiveOwnerThread() != current)
               return -1;
           // else we hold the exclusive lock; blocking here
           // would cause deadlock.
       } else if (readerShouldBlock()) {
           // Make sure we're not acquiring read lock reentrantly
           if (firstReader == current) {
               // assert firstReaderHoldCount > 0;
           } else {
           		// 当前线程不是第一个读者
               if (rh == null) {
                   rh = cachedHoldCounter;
                   // 获取当前线程的读重入次数
                   if (rh == null || rh.tid != getThreadId(current)) {
                       rh = readHolds.get();
                       if (rh.count == 0)
                           readHolds.remove();
                   }
               }
               if (rh.count == 0)
                   return -1;
           }
       }
       // 读锁个数超过上限
       if (sharedCount(c) == MAX_COUNT)
           throw new Error("Maximum lock count exceeded");
       // 再次尝试竞争资源
       if (compareAndSetState(c, c + SHARED_UNIT)) {
       		// 竞争成功
       		// 当前线程成为第一个读锁
           if (sharedCount(c) == 0) {
               firstReader = current;
               firstReaderHoldCount = 1;
           } else if (firstReader == current) {
               firstReaderHoldCount++;
           } else {
               if (rh == null)
                   rh = cachedHoldCounter;
               if (rh == null || rh.tid != getThreadId(current))
                   rh = readHolds.get();
               else if (rh.count == 0)
                   readHolds.set(rh);
               rh.count++;
               cachedHoldCounter = rh; // cache for release
           }
           return 1;
       }
   }
}
```
下面看一下readerShouldBlock()
首先看公平模式下的
如果等待队列中有元素的话,那么返回true
FairSync
```java
final boolean readerShouldBlock() {
    return hasQueuedPredecessors();
}
```
然后看不公平模式下的
NonFairSync
```java
final boolean readerShouldBlock() {
    /* As a heuristic to avoid indefinite writer starvation,
     * block if the thread that momentarily appears to be head
     * of queue, if one exists, is a waiting writer.  This is
     * only a probabilistic effect since a new reader will not
     * block if there is a waiting writer behind other enabled
     * readers that have not yet drained from the queue.
     */
    return apparentlyFirstQueuedIsExclusive();
}
```
下面是AQS中的源码
下面函数返回true的条件是:等待队列中有节点,并且第一个等待运行的任务是排他式任务,即写任务
即在不公平模式下,只要第一个等待运行的任务不是写任务,那么当前线程就可以成功地获得读锁
这样做的目的是防止写线程饥饿
```java
final boolean apparentlyFirstQueuedIsExclusive() {
   Node h, s;
   return (h = head) != null &&
       (s = h.next)  != null &&
       !s.isShared()         &&
       s.thread != null;
}
```
#### 释放
```java
protected final boolean tryReleaseShared(int unused) {
	// 首先获得当前线程
   Thread current = Thread.currentThread();
   // 当前线程是第一个读者
   if (firstReader == current) {
       // assert firstReaderHoldCount > 0;
       // 减少第一个读者的重入次数
       if (firstReaderHoldCount == 1)
           firstReader = null;
       else
           firstReaderHoldCount--;
   } else {
   		// 当前线程不是第一个读者
       HoldCounter rh = cachedHoldCounter;
       // 只有第一个读者和最近的读者的重入次数记录到了当前结构中
       // 其他读者的重入次数需要通过ThreadLocal来获取
       if (rh == null || rh.tid != getThreadId(current))
           rh = readHolds.get();
       int count = rh.count;
       // 减少当前线程的读重入次数
       if (count <= 1) {
           readHolds.remove();
           if (count <= 0)
               throw unmatchedUnlockException();
       }
       --rh.count;
   }
   // 循环加cas来更新状态
   for (;;) {
       int c = getState();
       int nextc = c - SHARED_UNIT;
       if (compareAndSetState(c, nextc))
           // Releasing the read lock has no effect on readers,
           // but it may allow waiting writers to proceed if
           // both read and write locks are now free.
           return nextc == 0;
   }
}
```
### 对应WriteLock的排他式操作
#### 获取
```java
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
     // 获取当前线程
    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;
}
```
下面看一下writeShouldBlock()
首先是公平模式下的
FairSync
```java
static final class FairSync extends Sync {
   private static final long serialVersionUID = -2274990926593161451L;
    // 等待队列中有其他线程正在等待,返回false
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
}
```
然后是不公平的
NonFairSync
直接返回false
```java
final boolean writerShouldBlock() {
     return false; // writers can always barge
 }
```
#### 释放
```java
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}
```

### 锁降级
锁降级指的是一个线程在独占写锁的情况下,可以继续占用读锁,并且当写锁释放时,该线程仍然占用读锁。
那么锁降级主要体现在哪里呢?
主要体现在在竞争读锁tryAcquieShare的时候,如果有线程获取了写锁,并不是立即返回竞争失败,而是先判断获取写锁的线程是否是当前线,如果是当前线程占用写锁,那么可以继续正常地进行读锁竞争,如果占用写锁的不是当前线程,那么直接返回竞争失败
那么锁降级有什么用呢?下面引用网上的话解释一下原因
主要是为了保证数据的可见性,如果当前线程不获取读锁而直接释放,假设这个时候,另一个线程(寄走线程T)获取写锁,并且更改了数据,那么,当前线程无法感知线程T的数据更新。如果当前线程按照锁降级的方式来获得读锁,那么线程T因为判断有线程正在占用读锁,所以不能成功获得写锁进而阻塞,直到当前线程使用数据并释放读锁和写锁之后,线程T才能使用写锁

猜你喜欢

转载自blog.csdn.net/lxlneversettle/article/details/88935449