0%

Java可重入读写锁源码分析

What is a ReadWriteLock

Basically, a ReadWriteLock is designed as a high-level locking mechanism that allows you to add thread-safety feature to a data structure while increasing throughput by allowing multiple threads to read the data concurrently and one thread to update the data exclusively.

Performance Goal

  • multiple threads can read the data at the same time as long as there is no thread updating the data
  • Only one thread can update the data at one time, causing other threads(both read and write thread) block until the write lock is released
  • if a thread attempts to update the data while other threads are reading the data, the write lock blocks until the read lock is released.

Usage

1
2
3
4
5
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//读锁
Lock readLock=rwLock.readLock();
//写锁
Lock writeLock=rwLock.writeLock();
1
2
3
try {
readLock.lock();
}catch...

Implementation Detail

读写锁共用Sync中的state变量来管理锁状态,高16位为读锁状态,低16位为写锁状态

例子:
0000000000000001 | 0000000000000000

0000000000000000 | 0000000000000001

ReadLock(读锁)

lock(获取锁的逻辑)

读锁获取锁的逻辑:如果写锁没有被其他线程占有,则成功获取锁,否则线程阻塞直到写锁被释放,具体如下

1. lock:调用sync的acquireShared()方法
1
public void lock() {sync.acquireShared(1);}
2. acquiredShared:调用tryAcquireShared尝试获取共享锁,若获取失败,则调用doAcquireShared阻塞直至被唤醒
1
2
3
4
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
3. tryAcquireShared:
  • 如果当前锁写状态不为0且占锁线程非当前线程,那么返回占锁失败的值-1。
  • 如果公平策略没有要求阻塞且重入数没有到达最大值,则直接尝试cas更新state。

a. 如果cas操作成功,有以下操作逻辑:
1)首先,如果当前读锁计数为0那么就设置第一个读线程就是当前线程。

2)其次,当前线程和firstReader同一个线程,记录firstReaderHoldCount也就是第一个读线程读锁定次数。

3)最后,读锁数量不为0并且不为当前线程,获取当前线程ThreadLocal当中的读锁重入计数器。结果返回占锁成功的值1

b. 如果cas操作失败,有以下操作逻辑:
通过fullTryAcquireShared尝试获取读锁,内部处理和tryAcquireShared过程相同

4. fullTryAcquireShared

逻辑同上,只是加了一个无限循环尝试获取共享锁

5. doAcquireShared

doAcquireShared用来处理读锁获取失败后等待的逻辑,doAcquireShared的内部的自旋保证了线程被唤醒后再次判断是否是第一个节点并尝试获取锁,失败再次进入休眠

1)将当前线程封装成Shared Node类型加入CLH队列

1
final Node node = addWaiter(Node.SHARED);

2)如果当前线程的Node节点是CLH队列的第一个节点则当前线程直接获取锁并开启读锁的扩散唤醒所有阻塞读锁的线程

1
setHeadAndPropagate(node, r);

3)如果当前线程的Node节点不是CLH队列的第一个节点那么就通过parkAndCheckInterrupt进入休眠

1
2
3
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;

unlock(释放锁的逻辑)

  • ReadLock的unlock()方法调用sync.releaseShared(1)方法进行释放。
  • 调用tryReleaseShared()方法尝试释放锁,如果释放成功,调用doReleaseShared尝试唤醒下一个节点

WriteLock(写锁)

好累,下次再继续写:>