自我表扬:《Dubbo 实现原理与源码解析 —— 精品合集》
表扬自己:《D数据库实体设计合集》

摘要: 原创出处 http://cmsblogs.com/?p=2213 「小明哥」欢迎转载,保留摘要,谢谢!

作为「小明哥」的忠实读者,「老艿艿」略作修改,记录在理解过程中,参考的资料。


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

此篇博客所有源码均来自 JDK 1.8

1. 简介

重入锁 ReentrantLock 是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而,读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。

读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:

  • 在同一时间,可以允许多个读线程同时访问。
  • 但是,在写线程访问时,所有读线程和写线程都会被阻塞。

读写锁的主要特性

  1. 公平性:支持公平性和非公平性。
  2. 重入性:支持重入。读写锁最多支持 65535 个递归写入锁和 65535 个递归读取锁。
  3. 锁降级:遵循获取锁,再获取锁,最后释放锁的次序,如此写锁能够降级成为读锁。

2. ReadWriteLock

java.util.concurrent.locks.ReadWriteLock ,读写锁接口。定义方法如下:

Lock readLock();

Lock writeLock();
  • 一对方法,分别获得锁和锁 Lock 对象。

3. ReentrantReadWriteLock

java.util.concurrent.locks.ReentrantReadWriteLock ,实现 ReadWriteLock 接口,可重入读写锁实现类。在它内部,维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 Writer 线程,读取锁可以由多个 Reader 线程同时保持。也就说说,写锁是独占的,读锁是共享的。

ReentrantReadWriteLock 类的大体结构如下:

/** 内部类  读锁 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 内部类 写锁 */
private final ReentrantReadWriteLock.WriteLock writerLock;

final Sync sync;

/** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
this(false);
}

/** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}

/** 返回用于写入操作的锁 */
@Override
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用于读取操作的锁 */
@Override
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }

abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* 省略其余源代码
*/
}
public static class WriteLock implements Lock, java.io.Serializable {
/**
* 省略其余源代码
*/
}

public static class ReadLock implements Lock, java.io.Serializable {
/**
* 省略其余源代码
*/
}
  • ReentrantReadWriteLock 与 ReentrantLock一样,其锁主体也是 Sync,它的读锁、写锁都是通过 Sync 来实现的。所以 ReentrantReadWriteLock 实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样。
  • 它的读写锁对应两个类:ReadLock 和 WriteLock 。这两个类都是 Lock 的子类实现。

在 ReentrantLock 中,使用 Sync ( 实际是 AQS )的 int 类型的 state 来表示同步状态,表示锁被一个线程重复获取的次数。但是,读写锁 ReentrantReadWriteLock 内部维护着一对读写锁,如果要用一个变量维护多种状态,需要采用“按位切割使用”的方式来维护这个变量,将其切分为两部分:高16为表示读,低16为表示写。

分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过位运算。假如当前同步状态为S,那么:

  • 写状态,等于 S & 0x0000FFFF(将高 16 位全部抹去)
  • 读状态,等于 S >>> 16 (无符号补 0 右移 16 位)。

代码如下:

// 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; // 每个锁的最大重入次数,65535
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; }
  • #exclusiveCount(int c) 静态方法,获得持有写状态的锁的次数。
  • #sharedCount(int c) 静态方法,获得持有读状态的锁的线程数量。不同于写锁,读锁可以同时多个线程持有。而每个线程持有的读锁支持重入的特性,所以需要对每个线程持有的读锁的数量单独计数,这就需要用到 HoldCounter 计数器。详细解析,见 「6. HoldCounter」

划分如下图:

FROM 《Java并发编程的艺术》的 「5.4 读写锁」 章节

划分

3.1 构造方法

在上面的构造方法中,我们已经看到基于 fair 参数,创建 创建 FairSync 还是 NonfairSync 对象。

3.2 getThreadId

#getThreadId(Thread thread) 静态方法,获得线程编号。代码如下:

 /**
* Returns the thread id for the given thread. We must access
* this directly rather than via method Thread.getId() because
* getId() is not final, and has been known to be overridden in
* ways that do not preserve unique mappings.
*/
static final long getThreadId(Thread thread) {
return UNSAFE.getLongVolatile(thread, TID_OFFSET);
}

// Unsafe mechanics
private static final sun.misc.Unsafe UNSAFE;
private static final long TID_OFFSET;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
TID_OFFSET = UNSAFE.objectFieldOffset
(tk.getDeclaredField("tid"));
} catch (Exception e) {
throw new Error(e);
}
}
  • 按道理说,直接调用线程对应的 Thread#getId() 方法,代码如下:

    private long tid;

    public long getId() {
    return tid;
    }
    • 但是实际上,Thread 的这个方法是final 修饰的,也就是说,如果我们有实现 Thread 的子类,完全可以覆写这个方法,所以可能导致无法获得 tid 属性。因此上面的方法,使用 Unsafe 直接获得 tid 属性。不愧是 JDK 的源码,细思极恐。
    • 另外,JDK-6346938 也讨论了 “java.lang.Thread.getId() should be final” 这个问题,目前已经被 JDK 认为是一个 BUG ,但是不造为什么一直没修复。

3.3 其他实现方法

ReentrantReadWriteLock 的实现方法,主要是调用 Sync 对应的方法,所以重点还是看下面部分的文章。

老艿艿:可以稍微看下,然后跳过先看下面部分的内容。

public final boolean isFair() {
return sync instanceof FairSync;
}

public int getReadLockCount() {
return sync.getReadLockCount();
}

public boolean isWriteLocked() {
return sync.isWriteLocked();
}
public boolean isWriteLockedByCurrentThread() {
return sync.isHeldExclusively();
}

public int getReadHoldCount() {
return sync.getReadHoldCount();
}

protected Collection<Thread> getQueuedWriterThreads() {
return sync.getExclusiveQueuedThreads();
}
protected Collection<Thread> getQueuedReaderThreads() {
return sync.getSharedQueuedThreads();
}
public final boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public final boolean hasQueuedThread(Thread thread) {
return sync.isQueued(thread);
}
public final int getQueueLength() {
return sync.getQueueLength();
}
protected Collection<Thread> getQueuedThreads() {
return sync.getQueuedThreads();
}

public boolean hasWaiters(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}
public int getWaitQueueLength(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition);
}
protected Collection<Thread> getWaitingThreads(Condition condition) {
if (condition == null)
throw new NullPointerException();
if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
throw new IllegalArgumentException("not owner");
return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition);
}

4. 读锁和写锁

在上文中,我们也提到,ReentrantReadWriteLock 的读锁和写锁,基于它内部的 Sync 实现,所以具体的实现方法,就是对内部的 Sync 的方法的调用

4.1 ReadLock

ReadLock 是 ReentrantReadWriteLock 的内部静态类,实现 java.util.concurrent.locks.Lock 接口,读锁实现类。

4.1.1 构造方法

private final Sync sync;

protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
  • sync 字段,通过 ReentrantReadWriteLock 的构造方法,传入并使用它的 Sync 对象。

4.1.2 lock

@Override
public void lock() {
sync.acquireShared(1);
}
  • 调用 AQS 的 #acquireShared(int arg) 方法,共享式获得同步状态。所以,读锁可以同时多个线程获取。

4.1.3 lockInterruptibly

@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}

4.1.4 tryLock

/**
* Acquires the read lock only if the write lock is not held by
* another thread at the time of invocation.
*
* <p>Acquires the read lock if the write lock is not held by
* another thread and returns immediately with the value
* {@code true}. Even when this lock has been set to use a
* fair ordering policy, a call to {@code tryLock()}
* <em>will</em> immediately acquire the read lock if it is
* available, whether or not other threads are currently
* waiting for the read lock. This &quot;barging&quot; behavior
* can be useful in certain circumstances, even though it
* breaks fairness. If you want to honor the fairness setting
* for this lock, then use {@link #tryLock(long, TimeUnit)
* tryLock(0, TimeUnit.SECONDS) } which is almost equivalent
* (it also detects interruption).
*
* <p>If the write lock is held by another thread then
* this method will return immediately with the value
* {@code false}.
*
* @return {@code true} if the read lock was acquired
*/
@Override
public boolean tryLock() {
return sync.tryReadLock();
}
  • 详细的说明,胖友可以看上面的英文注释。实际上,原因和 《【死磕 Java 并发】—– J.U.C 之重入锁:ReentrantLock》「4.4 tryLock」 相同。
  • 老艿艿的简单理解是:
    • #tryLock() 实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为 fair = true ( 使用公平锁 ),依然调用 Sync#tryReadLock() 方法。
    • 如果真的希望 #tryLock() 还是按照是否公平锁的方式来,可以调用 #tryLock(0, TimeUnit) 方法来实现。

4.1.5 tryLock

@Override
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

4.1.6 unlock

@Override
public void unlock() {
sync.releaseShared(1);
}
  • 调用 AQS 的 #releaseShared(int arg) 方法,共享式释放同步状态。

4.1.7 newCondition

@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
  • 不支持 Condition 条件。

4.2 WriteLock

WriteLock 的代码,类似 ReadLock 的代码,差别在于独占式获取同步状态。

WriteLock 是 ReentrantReadWriteLock 的内部静态类,实现 java.util.concurrent.locks.Lock 接口,写锁实现类。

4.2.1 构造方法

private final Sync sync;

protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
  • sync 字段,通过 ReentrantReadWriteLock 的构造方法,传入并使用它的 Sync 对象。

4.2.2 lock

@Override
public void lock() {
sync.acquire(1);
}
  • 调用 AQS 的 #.acquire(int arg) 方法,独占式获得同步状态。所以,写锁只能同时一个线程获取。

4.2.3 lockInterruptibly

@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

4.2.4 tryLock

 /**
* Acquires the write lock only if it is not held by another thread
* at the time of invocation.
*
* <p>Acquires the write lock if neither the read nor write lock
* are held by another thread
* and returns immediately with the value {@code true},
* setting the write lock hold count to one. Even when this lock has
* been set to use a fair ordering policy, a call to
* {@code tryLock()} <em>will</em> immediately acquire the
* lock if it is available, whether or not other threads are
* currently waiting for the write lock. This &quot;barging&quot;
* behavior can be useful in certain circumstances, even
* though it breaks fairness. If you want to honor the
* fairness setting for this lock, then use {@link
* #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) }
* which is almost equivalent (it also detects interruption).
*
* <p>If the current thread already holds this lock then the
* hold count is incremented by one and the method returns
* {@code true}.
*
* <p>If the lock is held by another thread then this method
* will return immediately with the value {@code false}.
*
* @return {@code true} if the lock was free and was acquired
* by the current thread, or the write lock was already held
* by the current thread; and {@code false} otherwise.
*/
@Override
public boolean tryLock( ) {
return sync.tryWriteLock();
}
  • 详细的说明,胖友可以看上面的英文注释。实际上,原因和 《【死磕 Java 并发】—– J.U.C 之重入锁:ReentrantLock》「4.4 tryLock」 相同。
    • 老艿艿的简单理解是:
      • #tryLock() 实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为 fair = true ( 使用公平锁 ),依然调用 Sync#tryWriteLock() 方法。
      • 如果真的希望 #tryLock() 还是按照是否公平锁的方式来,可以调用 #tryLock(0, TimeUnit) 方法来实现。

4.2.5 tryLock

@Override 
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}

4.2.6 unlock

@Override
public void unlock() {
sync.release(1);
}
  • 调用 AQS 的 #release(int arg) 方法,独占式释放同步状态。

4.2.7 newCondition

@Override
public Condition newCondition() {
return sync.newCondition();
}
  • 调用 Sync#newCondition() 方法,创建 Condition 对象。

4.2.8 isHeldByCurrentThread

public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
  • 调用 Sync#isHeldExclusively() 方法,判断是否被当前线程独占锁。

4.2.9 getHoldCount

public int getHoldCount() {
return sync.getWriteHoldCount();
}
  • 调用 Sync#getWriteHoldCount() 方法,返回当前线程独占锁的持有数量。

5. Sync 抽象类

Sync 是 ReentrantReadWriteLock 的内部静态类,实现 AbstractQueuedSynchronizer 抽象类,同步器抽象类。它使用 AQS 的 state 字段,来表示当前锁的持有数量,从而实现可重入读写锁的特性。

4.1 构造方法

private transient ThreadLocalHoldCounter readHolds; // 当前线程的读锁持有数量

private transient Thread firstReader = null; // 第一个获取读锁的线程
private transient int firstReaderHoldCount; // 第一个获取读锁的重入数

private transient HoldCounter cachedHoldCounter; // 最后一个获得读锁的线程的 HoldCounter 的缓存对象

Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}

4.2 writerShouldBlock

abstract boolean writerShouldBlock();
  • 获取写锁时,如果有前序节点也获得锁时,是否阻塞。NonefairSync 和 FairSync 下有不同的实现。详细解析,见 「6. Sync 实现类」

4.3 readerShouldBlock

abstract boolean readerShouldBlock();
  • 获取读锁时,如果有前序节点也获得锁时,是否阻塞。NonefairSync 和 FairSync 下有不同的实现。详细解析,见 「6. Sync 实现类」

4.4 【写锁】tryAcquire

@Override
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//当前锁个数
int c = getState();
//写锁
int w = exclusiveCount(c);
if (c != 0) {
//c != 0 && w == 0 表示存在读锁
//当前线程不是已经获取写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超出最大范围
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
// 是否需要阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置获取锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
  • 该方法和 ReentrantLock 的 #tryAcquire(int arg) 大致一样,差别在判断重入时,增加了一项条件:读锁是否存在。因为要确保写锁的操作对读锁是可见的。如果在存在读锁的情况下允许获取写锁,那么那些已经获取读锁的其他线程可能就无法感知当前写线程的操作。因此只有等读锁完全释放后,写锁才能够被当前线程所获取,一旦写锁获取了,所有其他读、写线程均会被阻塞。
  • 调用 #writerShouldBlock() 抽象方法,若返回 true ,则获取写锁失败

4.5 【读锁】tryAcquireShared

#tryAcqurireShared(int arg) 方法,尝试获取读同步状态,获取成功返回 >= 0 的结果,否则返回 < 0 的结果。代码如下:

protected final int tryAcquireShared(int unused) {
//当前线程
Thread current = Thread.currentThread();
int c = getState();
//exclusiveCount(c)计算写锁
//如果存在写锁,且锁的持有者不是当前线程,直接返回-1
//存在锁降级问题,后续阐述
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//读锁
int r = sharedCount(c);

/*
* readerShouldBlock():读锁是否需要等待(公平锁原则)
* r < MAX_COUNT:持有线程小于最大数(65535)
* compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态
*/
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) { //修改高16位的状态,所以要加上2^16
/*
* holdCount部分后面讲解
*/
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);
}
  • 读锁获取的过程相对于独占锁而言会稍微复杂下,整个过程如下:
    1. 因为存在锁降级情况,如果存在写锁且锁的持有者不是当前线程,则直接返回失败,否则继续。
    2. 依据公平性原则,调用 #readerShouldBlock() 方法来判断读锁是否不需要阻塞,读锁持有线程数小于最大值(65535),且 CAS 设置锁状态成功,执行以下代码(对于 HoldCounter ,见 「6. HoldCounter」 中),并返回 1 。如果不满足任一条件,则调用 #fullTryAcquireShared(Thread thread) 方法,详细解析,见 「4.5.1 fullTryAcquireShared」 中。

4.5.1 fullTryAcquireShared

final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) {
int c = getState();
// 锁降级
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
}
// 读锁需要阻塞,判断是否当前线程已经获取到读锁
else if (readerShouldBlock()) {
//列头为当前线程
if (firstReader == current) {
}
//HoldCounter后面讲解
else {
if (rh == null) {
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0) // 计数为 0 ,说明没得到读锁,清空线程变量
readHolds.remove();
}
}
if (rh.count == 0) // 说明没得到读锁
return -1;
}
}
//读锁超出最大范围
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//CAS设置读锁成功
if (compareAndSetState(c, c + SHARED_UNIT)) { //修改高16位的状态,所以要加上2^16
//如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
}
//如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,则将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;
}
}
}
  • 该方法会根据“是否需要阻塞等待”,“读取锁的共享计数是否超过限制”等等进行处理。如果不需要阻塞等待,并且锁的共享计数没有超过限制,则通过 CAS 尝试获取锁,并返回 1 。所以,#fullTryAcquireShared(Thread) 方法,是 #tryAcquireShared(int unused) 方法的自旋重试的逻辑。

4.6 【写锁】tryRelease

protected final boolean tryRelease(int releases) {
//释放的线程不为锁的持有者
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
//若写锁的新线程数为0,则将锁的持有者设置为null
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
  • 写锁释放锁的整个过程,和独占锁 ReentrantLock 相似,每次释放均是减少写状态,当写状态为 0 时,表示写锁已经完全释放了,从而让等待的其他线程可以继续访问读、写锁,获取同步状态。同时,此次写线程的修改对后续的线程可见

4.7 【读锁】tryReleaseShared

protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//如果想要释放锁的线程为第一个获取锁的线程
if (firstReader == current) {
//仅获取了一次,则需要将firstReader 设置null,否则 firstReaderHoldCount - 1
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
}
//获取rh对象,并更新“当前线程获取锁的信息”
else {
HoldCounter rh = cachedHoldCounter;
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))
return nextc == 0;
}
}
  • #unmatchedUnlockException() 方法,返回 IllegalMonitorStateException 异常。代码如下:

    private IllegalMonitorStateException unmatchedUnlockException() {
    return new IllegalMonitorStateException(
    "attempt to unlock read lock, not locked by current thread");
    }
    • 出现的情况是,unlock 读锁的线程,非获得读锁的线程。正常使用的情况,不会出现该情况。

4.8 tryWriteLock

#tryWriteLock() 方法,尝试获取读锁。

  • 若获取成功,返回 true 。
  • 若失败,返回 false 即可,不进行等待排队

代码如下:

final boolean tryWriteLock(){
Thread current = Thread.currentThread();
int c = getState();
if(c != 0){
int w = exclusiveCount(c); // 获得现在写锁获取的数量
if(w == 0 || current != getExclusiveOwnerThread()){ // 判断是否是其他的线程获取了写锁。若是,返回 false
return false;
}
if(w == MAX_COUNT){ // 超过写锁上限,抛出 Error 错误
throw new Error("Maximum lock count exceeded");
}
}

if(!compareAndSetState(c, c + 1)){ // CAS 设置同步状态,尝试获取写锁。若失败,返回 false
return false;
}
setExclusiveOwnerThread(current); // 设置持有写锁为当前线程
return true;
}

4.9 tryReadLock

#tryReadLock() 方法,尝试获取写锁。

  • 若获取成功,返回 true 。
  • 若失败,返回 false 即可,不进行等待排队

代码如下:

final boolean tryReadLock() {
Thread current = Thread.currentThread();
for (;;) {
int c = getState();
//exclusiveCount(c)计算写锁
//如果存在写锁,且锁的持有者不是当前线程,直接返回-1
//存在锁降级问题,后续阐述
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return false;
// 读锁
int r = sharedCount(c);
/*
* HoldCount 部分后面讲解
*/
if (r == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
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 != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return true;
}
}
}

4.10 isHeldExclusively

protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}

4.11 newCondition

final ConditionObject newCondition() {
return new ConditionObject();
}

4.12 其他实现方法

其它实现方法,比较简单,胖友自己查看。

final Thread getOwner() {
// Must read state before owner to ensure memory consistency
return ((exclusiveCount(getState()) == 0) ?
null :
getExclusiveOwnerThread());
}

final int getReadLockCount() {
return sharedCount(getState());
}

final boolean isWriteLocked() {
return exclusiveCount(getState()) != 0;
}

final int getWriteHoldCount() {
return isHeldExclusively() ? exclusiveCount(getState()) : 0;
}

final int getReadHoldCount() {
if (getReadLockCount() == 0)
return 0;

Thread current = Thread.currentThread();
if (firstReader == current)
return firstReaderHoldCount;

HoldCounter rh = cachedHoldCounter;
if (rh != null && rh.tid == getThreadId(current))
return rh.count;

int count = readHolds.get().count;
if (count == 0) readHolds.remove();
return count;
}

/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
readHolds = new ThreadLocalHoldCounter();
setState(0); // reset to unlocked state
}

final int getCount() { return getState(); }

5. Sync 实现类

5.1 NonfairSync

NonfairSync 是 ReentrantReadWriteLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。代码如下:

static final class NonfairSync extends Sync {

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

@Override
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 的 #apparentlyFirstQueuedIsExclusive() 方法,判断是否当前写锁已经被获取。代码如下:

    final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null &&
    (s = h.next) != null &&
    !s.isShared() && // 非共享,即独占
    s.thread != null;
    }

5.2 FairSync

FairSync 是 ReentrantReadWriteLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。代码如下:

static final class FairSync extends Sync {

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

@Override
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}

}
  • 调用 AQS 的 #hasQueuedPredecessors() 方法,是否有前序节点,即自己不是首个等待获取同步状态的节点。

6. HoldCounter

在读锁获取锁和释放锁的过程中,我们一直都可以看到一个变量 rh (HoldCounter ),该变量在读锁中扮演着非常重要的作用。

我们了解读锁的内在机制其实就是一个共享锁,为了更好理解 HoldCounter ,我们暂且认为它不是一个锁的概率,而相当于一个计数器。一次共享锁的操作就相当于在该计数器的操作。获取共享锁,则该计数器 + 1,释放共享锁,该计数器 - 1。只有当线程获取共享锁后才能对共享锁进行释放、重入操作。所以 HoldCounter 的作用就是当前线程持有共享锁的数量,这个数量必须要与线程绑定在一起,否则操作其他线程锁就会抛出异常

我们先看 HoldCounter 的代码:

HoldCounter 是 Sync 的内部静态类。

static final class HoldCounter {
int count = 0; // 计数器
final long tid = getThreadId(Thread.currentThread()); // 线程编号
}
  • HoldCounter 定义非常简单,就是一个计数器 count和线程编号 tid 两个变量。按照这个意思我们看到 HoldCounter 是需要和某给线程进行绑定了。我们知道如果要将一个对象和线程绑定仅仅有 tid 是不够的,而且从上面的代码我们可以看到 HoldCounter 仅仅只是记录了 tid ,根本起不到绑定线程的作用。那么怎么实现呢?答案是实现 ThreadLocal 的 ThreadLocalHoldCounter 类,代码如下:

    ThreadLocalHoldCounter 是 Sync 的内部静态类。

    static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {

    @Override
    public HoldCounter initialValue() {
    return new HoldCounter();
    }

    }

通过 ThreadLocalHoldCounter 类,HoldCounter 就可以与线程进行绑定了。故而,HoldCounter 应该就是绑定线程上的一个计数器,而 ThreadLocalHoldCounter 则是线程绑定的 ThreadLocal。从上面我们可以看到 ThreadLocal 将 HoldCounter 绑定到当前线程上,同时 HoldCounter 也持有线程编号,这样在释放锁的时候才能知道 ReadWriteLock 里面缓存的上一个读取线程(cachedHoldCounter)是否是当前线程。这样做的好处是可以减少ThreadLocal.get() 方法的次调用数,因为这也是一个耗时操作。需要说明的是这样HoldCounter 绑定线程编号而不绑定线程对象的原因是,避免 HoldCounter 和 ThreadLocal 互相绑定而导致 GC 难以释放它们(尽管 GC 能够智能的发现这种引用而回收它们,但是这需要一定的代价),所以其实这样做只是为了帮助 GC 快速回收对象而已。

看到这里我们明白了 HoldCounter 作用了,我们在看一个获取读锁的代码段:

//如果获取读锁的线程为第一次获取读锁的线程,则firstReaderHoldCount重入数 + 1
else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//非firstReader计数
if (rh == null)
rh = cachedHoldCounter;
//rh == null 或者 rh.tid != current.getId(),需要获取rh
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
//加入到readHolds中
else if (rh.count == 0)
readHolds.set(rh);
//计数+1
rh.count++;
cachedHoldCounter = rh; // cache for release
}
  • 这里解释下为何要引入 firstReaderfirstReaderHoldCount 变量。这是为了一个效率问题,firstReader 是不会放入到 readHolds 中的,如果读锁仅有一个的情况下,就会避免查找 readHolds

7. 锁降级

在本文开篇,LZ 就阐述了读写锁有一个特性就是锁降级。锁降级就意味着写锁是可以降级为读锁的,但是需要遵循先获取写锁、获取读锁在释放写锁的次序。注意如果当前线程先获取写锁,然后释放写锁,再获取读锁这个过程不能称之为锁降级,锁降级一定要遵循那个次序。

在获取读锁的方法 #tryAcquireShared(int unused) 中,有一段代码就是来判读锁降级的:

int c = getState();
//exclusiveCount(c)计算写锁
//如果存在写锁,且锁的持有者不是当前线程,直接返回-1
//存在锁降级问题,后续阐述
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//读锁
int r = sharedCount(c);

锁降级中读锁的获取释放为必要?肯定是必要的。试想,假如当前线程 A 不获取读锁而是直接释放了写锁,这个时候另外一个线程 B 获取了写锁,那么这个线程 B 对数据的修改是不会对当前线程 A 可见的。如果获取了读锁,则线程B在获取写锁过程中判断如果有读锁还没有释放则会被阻塞,只有当前线程 A 释放读锁后,线程 B 才会获取写锁成功。

参考资料

  1. Doug Lea:《Java并发编程实战》
  2. 方腾飞:《Java并发编程的艺术》的 「5.4 读写锁」 章节
  3. 《Java多线程(十)之 ReentrantReadWriteLock 深入分析》

666. 彩蛋

如果你对 Java 并发感兴趣,欢迎加入我的知识星球一起交流。

知识星球

文章目录
  1. 1. 1. 简介
  2. 2. 2. ReadWriteLock
  3. 3. 3. ReentrantReadWriteLock
    1. 3.1. 3.1 构造方法
    2. 3.2. 3.2 getThreadId
    3. 3.3. 3.3 其他实现方法
  4. 4. 4. 读锁和写锁
    1. 4.1. 4.1 ReadLock
      1. 4.1.1. 4.1.1 构造方法
      2. 4.1.2. 4.1.2 lock
      3. 4.1.3. 4.1.3 lockInterruptibly
      4. 4.1.4. 4.1.4 tryLock
      5. 4.1.5. 4.1.5 tryLock
      6. 4.1.6. 4.1.6 unlock
      7. 4.1.7. 4.1.7 newCondition
    2. 4.2. 4.2 WriteLock
      1. 4.2.1. 4.2.1 构造方法
      2. 4.2.2. 4.2.2 lock
      3. 4.2.3. 4.2.3 lockInterruptibly
      4. 4.2.4. 4.2.4 tryLock
      5. 4.2.5. 4.2.5 tryLock
      6. 4.2.6. 4.2.6 unlock
      7. 4.2.7. 4.2.7 newCondition
      8. 4.2.8. 4.2.8 isHeldByCurrentThread
      9. 4.2.9. 4.2.9 getHoldCount
  5. 5. 5. Sync 抽象类
    1. 5.1. 4.1 构造方法
    2. 5.2. 4.2 writerShouldBlock
    3. 5.3. 4.3 readerShouldBlock
    4. 5.4. 4.4 【写锁】tryAcquire
    5. 5.5. 4.5 【读锁】tryAcquireShared
      1. 5.5.1. 4.5.1 fullTryAcquireShared
    6. 5.6. 4.6 【写锁】tryRelease
    7. 5.7. 4.7 【读锁】tryReleaseShared
    8. 5.8. 4.8 tryWriteLock
    9. 5.9. 4.9 tryReadLock
    10. 5.10. 4.10 isHeldExclusively
    11. 5.11. 4.11 newCondition
    12. 5.12. 4.12 其他实现方法
  6. 6. 5. Sync 实现类
    1. 6.1. 5.1 NonfairSync
    2. 6.2. 5.2 FairSync
  7. 7. 6. HoldCounter
  8. 8. 7. 锁降级
  9. 9. 参考资料
  10. 10. 666. 彩蛋