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

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

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


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

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

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

1. 简介

ReentrantLock,可重入锁,是一种递归无阻塞的同步机制。它可以等同于 synchronized 的使用,但是 ReentrantLock 提供了比 synchronized 更强大、灵活的锁机制,可以减少死锁发生的概率。

API 介绍如下:

一个可重入的互斥锁定 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。

ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 #isHeldByCurrentThread()#getHoldCount() 方法来检查此情况是否发生。

ReentrantLock 还提供了公平锁非公平锁的选择,通过构造方法接受一个可选的 fair 参数(默认非公平锁):当设置为 true 时,表示公平锁;否则为非公平锁。

公平锁与非公平锁的区别在于,公平锁的锁获取是有顺序的。但是公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。

ReentrantLock 整体结构如下图:

201702010001

  • ReentrantLock 实现 Lock 接口,基于内部的 Sync 实现。
  • Sync 实现 AQS ,提供了 FairSync 和 NonFairSync 两种实现。

2. Sync 抽象类

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

2.1 lock

/**
* Performs {@link Lock#lock}. The main reason for subclassing
* is to allow fast path for nonfair version.
*/
abstract void lock();
  • 执行锁。抽象了该方法的原因是,允许子类实现快速获得非公平锁的逻辑。

2.2 nonfairTryAcquire

#nonfairTryAcquire(int acquires) 方法,非公平锁的方式获得锁。代码如下:

final boolean nonfairTryAcquire(int acquires) {
//当前线程
final Thread current = Thread.currentThread();
//获取同步状态
int c = getState();
//state == 0,表示没有该锁处于空闲状态
if (c == 0) {
//获取锁成功,设置为当前线程所有
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//线程重入
//判断锁持有的线程是否为当前线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
  • 该方法主要逻辑:首先判断同步状态 state == 0 ?
    • 如果,表示该锁还没有被线程持有,直接通过CAS获取同步状态。
      • 如果成功,返回 true 。
      • 否则,返回 false 。
    • 如果不是,则判断当前线程是否为获取锁的线程
      • 如果是,则获取锁,成功返回 true 。成功获取锁的线程,再次获取锁,这是增加了同步状态 state 。通过这里的实现,我们可以看到上面提到的 “它使用 AQS 的 state 字段,来表示当前锁的持有数量,从而实现可重入的特性”。
      • 否则,返回 false 。
  • 理论来说,这个方法应该在子类 FairSync 中实现,但是为什么会在这里呢?在下文的 ReentrantLock.tryLock() 中,详细解析。

2.3 tryRelease

#tryRelease(int releases) 实现方法,释放锁。代码如下:

protected final boolean tryRelease(int releases) {
// 减掉releases
int c = getState() - releases;
// 如果释放的不是持有锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// state == 0 表示已经释放完全了,其他线程可以获取同步状态了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
  • 通过判断判断是否为获得到锁的线程,保证该方法线程安全。
  • 只有当同步状态彻底释放后,该方法才会返回 true 。当 state == 0 时,则将锁持有线程设置为 null ,free= true,表示释放成功。

2.4 其他实现方法

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

// 是否当前线程独占
@Override
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();
}

// 新生成条件
final ConditionObject newCondition() {
return new ConditionObject();
}

// Methods relayed from outer class

// 获得占用同步状态的线程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}

// 获得当前线程持有锁的数量
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}

// 是否被锁定
final boolean isLocked() {
return getState() != 0;
}

/**
* Reconstitutes the instance from a stream (that is, deserializes it).
* 自定义反序列化逻辑
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
  • 从这些方法中,我们可以看到,ReentrantLock 是独占获取同步状态的模式。

3. Sync 实现类

3.1 NonfairSync

NonfairSync 是 ReentrantLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。

3.1.1 lock

#lock() 实现方法,首先基于 AQS state 进行 CAS 操作,将 0 => 1 。若成功,则获取锁成功。若失败,执行 AQS 的正常的同步状态获取逻辑。代码如下:

@Override
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
  • 优先基于 AQS state 进行 CAS 操作,已经能体现出非公平锁的特点。因为,此时有可能有 N + 1 个线程正在获得锁,其中 1 个线程已经获得到锁,释放的瞬间,恰好被的线程抢夺到,而不是排队的 N 个线程。

3.1.2 tryAcquire

#tryAcquire(int acquires) 实现方法,非公平的方式,获得同步状态。代码如下:

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
  • 直接调用 #nonfairTryAcquire(int acquires) 方法,非公平锁的方式获得锁。

3.2 FairSync

FairSync 是 ReentrantLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。

3.2.1 lock

#lock() 实现方法,代码如下:

final void lock() {
acquire(1);
}
  • 直接执行 AQS 的正常的同步状态获取逻辑。

3.2.2 tryAcquire

#tryAcquire(int acquires) 实现方法,公平的方式,获得同步状态。代码如下:

protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() && // <1>
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

比较非公平锁和公平锁获取同步状态的过程,会发现两者唯一的区别就在于,公平锁在获取同步状态时多了一个限制条件 <1> 处的 #hasQueuedPredecessors() 方法,是否有前序节点,即自己不是首个等待获取同步状态的节点。代码如下:

// AbstractQueuedSynchronizer.java
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾节点
Node h = head; //头节点
Node s;

//头节点 != 尾节点
//同步队列第一个节点不为null
//当前线程是同步队列第一个节点
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
  • 该方法主要做一件事情:主要是判断当前线程是否位于 CLH 同步队列中的第一个。如果是则返回 true ,否则返回 false 。

3. Lock 接口

java.util.concurrent.locks.Lock 接口,定义方法如下:

void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

void unlock();

Condition newCondition();

4. ReentrantLock

java.util.concurrent.locks.ReentrantLock ,实现 Lock 接口,重入锁。

ReentrantLock 的实现方法,基本是对 Sync 的调用。

4.1 构造方法

public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
  • 基于 fair 参数,创建 FairSync 还是 NonfairSync 对象。

4.2 lock

@Override
public void lock() {
sync.lock();
}

4.3 lockInterruptibly

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

4.4 tryLock

/**
* Acquires the lock only if it is not held by another thread at the time
* of invocation.
*
* <p>Acquires the lock if it is not held by another thread and
* returns immediately with the value {@code true}, setting the
* 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 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 lock was already held by the current
* thread; and {@code false} otherwise
*/
@Override
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
  • 详细的说明,胖友可以看上面的英文注释。
  • 老艿艿的简单理解是:
    • #tryLock() 实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为 fair = true ( 使用公平锁 ),依然调用 Sync#nonfairTryAcquire(int acquires) 方法。
    • 如果真的希望 #tryLock() 还是按照是否公平锁的方式来,可以调用 #tryLock(0, TimeUnit) 方法来实现。

4.5 tryLock

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

4.6 unlock

@Override
public void unlock() {
sync.release(1);
}

4.7 newCondition

@Override
public Condition newCondition() {
return sync.newCondition();
}

4.8 其他实现方法

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

public int getHoldCount() {
return sync.getHoldCount();
}
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
public boolean isLocked() {
return sync.isLocked();
}


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

protected Thread getOwner() {
return sync.getOwner();
}
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);
}

5. ReentrantLock 与 synchronized 的区别

前面提到 ReentrantLock 提供了比 synchronized 更加灵活和强大的锁机制,那么它的灵活和强大之处在哪里呢?他们之间又有什么相异之处呢?

首先他们肯定具有相同的功能和内存语义。

  1. synchronized 相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。
  2. ReentrantLock 还提供了条件 Condition ,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合(以后会阐述Condition)。
  3. ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而 synchronized 则一旦进入锁请求要么成功要么阻塞,所以相比 synchronized 而言,ReentrantLock会不容易产生死锁些。
  4. ReentrantLock 支持更加灵活的同步代码块,但是使用 synchronized 时,只能在同一个 synchronized 块结构中获取和释放。注意,ReentrantLock 的锁释放一定要在 finally 中处理,否则可能会产生严重的后果。
  5. ReentrantLock 支持中断处理,且性能较 synchronized 会好些。

参考资料

  1. Doug Lea:《Java并发编程实战》
  2. 方腾飞:《Java并发编程的艺术》的 「5.1 Lock 接口」「5.3 重入锁」 章节
  3. 《【JUC】JDK 1.8 源码分析之 ReentrantLock(三)》

666. 彩蛋

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

知识星球

文章目录
  1. 1. 1. 简介
  2. 2. 2. Sync 抽象类
    1. 2.1. 2.1 lock
    2. 2.2. 2.2 nonfairTryAcquire
    3. 2.3. 2.3 tryRelease
    4. 2.4. 2.4 其他实现方法
  3. 3. 3. Sync 实现类
    1. 3.1. 3.1 NonfairSync
      1. 3.1.1. 3.1.1 lock
      2. 3.1.2. 3.1.2 tryAcquire
    2. 3.2. 3.2 FairSync
      1. 3.2.1. 3.2.1 lock
      2. 3.2.2. 3.2.2 tryAcquire
  4. 4. 3. Lock 接口
  5. 5. 4. ReentrantLock
    1. 5.1. 4.1 构造方法
    2. 5.2. 4.2 lock
    3. 5.3. 4.3 lockInterruptibly
    4. 5.4. 4.4 tryLock
    5. 5.5. 4.5 tryLock
    6. 5.6. 4.6 unlock
    7. 5.7. 4.7 newCondition
    8. 5.8. 4.8 其他实现方法
  6. 6. 5. ReentrantLock 与 synchronized 的区别
  7. 7. 参考资料
  8. 8. 666. 彩蛋