一、简介
ReentrantLock是JDK提供给开发者的一个工具锁,基于AQS(AbstractQueuedSynchronizer)实现,可用于synchronized同功能替换。
其内部提供公平和非公平两种实现方式(Synchronized只有非公平),未获取到锁的线程会加入队列(公平锁和非公平锁加入队列的方式不同),由AQS维护双向队列。
二、简单示例
public static void main(String[] args) {
testReentrantLock();
}
private static void testReentrantLock() {
ReentrantLock lock = new ReentrantLock();
new Thread(() -> {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "准备获取锁。。。");
lock.lock();
System.out.println(threadName + "获取锁成功。。。");
try {
Thread.sleep(5000);
} catch (Exception e) {
System.out.println(threadName + "休眠失败。。。");
}
System.out.println(threadName + "释放锁。。。");
lock.unlock();
}, "线程1").start();
new Thread(() -> {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + "准备获取锁。。。");
lock.lock();
System.out.println(threadName + "获取锁成功。。。");
try {
Thread.sleep(1000);
} catch (Exception e) {
System.out.println(threadName + "休眠失败。。。");
}
System.out.println(threadName + "释放锁。。。");
lock.unlock();
}, "线程2").start();
}
运行结果
线程1准备获取锁。。。
线程1获取锁成功。。。
线程2准备获取锁。。。
线程1释放锁。。。
线程2获取锁成功。。。
线程2释放锁。。。
三、原理分析
- 1)ReentrantLock内部维护3个十分重要的属性
state:默认0
head:双向链表头节点,类型为Node tail:双向链表尾节点,类型为Node
- 2)案例说明相关方法
上锁方法: lock()
释放锁方法: unlock()
非公平锁
1、使用方式
// 方式1
ReentrantLock rLock = new ReentrantLock();
// 内部创建一个继承自AQS的sync对象
// sync = new NonfairSync();
// 方式2
ReentrantLock rLock = new ReentrantLock(false);
// 内部创建一个继承自AQS的sync对象,实现类为NonFairSync
// sync = fair ? new FairSync() : new NonfairSync();
2、原理分析
- 2.1 上锁
// 说明:
// 1、客户端调用reentrantLock的lock方法
rLock.lock();
// 2、方法委托Sync实例sync调用lock方法
// 2.1 Sync继承自AbstractQueuedSynchronizer
// 2.2 Sync的lock方法是个抽象方法
sync.lcok();
// 3、子类NonfairSync重写lock方法
ReentrantLock.NonfairSync.lock();
- 2.2.1 NonfairSync重写后的lock方法
final void lock() {
// 1、CAS设置state(volatile变量,默认为0)为1
if (compareAndSetState(0, 1))
// 2、成功:已经获取到锁,设置独占线程为当前线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 3、失败:未获取到锁,继续获取锁
acquire(1);
}
注意:对于交替执行的场景,走到步骤2即可返回,而对于存在竞争的场景,CAS失败后,会执行步骤3,继续尝试获取锁
- 2.2.2 acquire方法,继续获取锁
public final void acquire(int arg) {
if (
// 1、尝试获取锁
!tryAcquire(arg)
&&
// 2、失败:加入AQS队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
// 3、成功:返回
}
- 2.2.3 tryAcquire方法,尝试获取锁
// 默认抛出异常,需要子类重写
// NonfairSync重写tryAcquire方法
// 最终调用父类Sync的nonfairTryAcquire方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 1、获取state变量值
int c = getState();
if (c == 0) {
// 2、默认为0(上文提到),此时表明未被上锁或者其他线程正好释放锁
// 2.1、再次CAS设置state为1(acquires)
if (compareAndSetState(0, acquires)) {
// 2.1.1、成功:已经获取到锁,设置独占线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 2、已被上锁且上锁线程为当前线程
// 2.1 成功,重入:state += acquires
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 3、失败,已被上锁且非当前线程上的锁,或者2.1CAS失败
return false;
}
若上述acquire方法获取锁成功,直接返回,否则,加入AQS队列,继续获取锁,即:步骤1返回false,会继续步骤2方法调用,回到上一步调用逻辑,如下所示:调用acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法
public final void acquire(int arg) {
if (
// 1、尝试获取锁-失败
!tryAcquire(arg)
&&
// 2、失败:加入AQS队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 2.2.4 addWaiter构建节点并入队列
节点类型:Node
Node类属性(只讨论和lock相关的):waitStatus(默认为0)
prev(上一个节点,默认为空) next(下一个节点,默认为空) thread(等待锁的线程,默认为空) nextWaiter(默认为空)
Node结构如图
调用方法前,head和tail均为null,结构如图1所示:
private Node addWaiter(Node mode) {
// 创建节点时:thread为当前线程,nextWaiter为独占模式(mode)
Node node = new Node(Thread.currentThread(), mode);
// 大体逻辑:
// 若队列已构建,CAS设置node为尾节点,并维护node双向队列
// 若队列未构建,或者队列已构建且CAS失败,调用enq方法维护双向队列
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- 2.2.5 调用enq方法维护双向队列
private Node enq(final Node node) {
// 无限循环,消耗CPU
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 首次进入,队列为空,CAS设置头节点
// Node所有值均为默认值
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 待入队的真实节点构建双向队列
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
假设有三个线程,t1线程获取到了锁,t2,t3线程未获取到锁,且按照t2再t3加入队列的顺序,node2节点维护t2,node3节点维护t3,执行完addWaiter方法之后,结果如下图示,node3节点也是tail节点
t2加入队列,head节点的next指向node2,node2节点的prev节点指向head,同理,t3加入队列,node2节点的next指向node3,node3节点的prev节点指向node2
- 2.2.6 acquireQueued入队列
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 无限循环
for (;;) {
// 若当前节点是head后的一个节点,尝试获取锁tryAcquire
// 或者
// 其他线程释放锁后,唤醒
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
// 获取锁成功:清空(参考下图1)
// a)创建是赋值的thread
// b)入队列设置的prev和next
// c)保留nextWaiter(lock的流程中无需关注)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取锁失败,会修改p的waitStatus=-1(参考下图2),然后再次回到for循环获取锁
// 若仍无法获取锁,park当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
图1
图2
2.2 释放锁
- 2.2.1 调用unlock方法
// 说明:
// 1、客户端调用reentrantLock的unlock方法
rLock.unlock();
// 2、方法委托Sync实例sync调用release方法
sync.release(1);
// 3、sync未重写该方法,由父类AbstractQueuedSynchronizer实现
AQS.release();
- 2.2.2 AbstractQueuedSynchronizer的release方法
public final boolean release(int arg) {
// 1、尝试释放锁
if (tryRelease(arg)) {
// 2、释放成功
// 2.1 没有竞争,h == null,直接返回
// 2.2 有竞争,h != nul,由lock分析可知,waitStatus = -1,故而会执行unpark,可回到‘acquireQueued入队列’查看
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
// 释放失败或者未完全释放(重入的场景)
return false;
}
- 2.2.3 tryRelease方法,尝试释放锁
// 由子类Sync实现
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 释放releases
if (Thread.currentThread() != getExclusiveOwnerThread())
// 报错:非当前线程获取的锁,不能由当前线程释放
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 重入释放完全或者简单lock后释放完全
free = true;
// 清空锁的独占线程
setExclusiveOwnerThread(null);
}
// 重置state值
setState(c);
return free;
}
注意:参考# 2.2.6 acquireQueued入队列 #查看,通过park唤醒头节点后的第一个节点线程,该线程会再次尝试获取锁,若获取成功,会从队列移除该节点。