简单介绍
ReentrantLock是一个可重入且独占式的锁,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能。ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制。除此之外,该锁还支持获取锁时的公平和非公平选择。
ReentrantLock底层基于AQS实现锁机制。
ReentrantLock中存在抽象类Sync,有两个实现类FairSync公平锁和NonfairSync非公平锁。
AQS全名AbstractQueuedSynchronizer,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch。
ReentrantLock常用api
//无参构造默认生成NonfairSync即非公平锁
public ReentrantLock();
//带参构造fair true为FairSync,false为NonfairSync
public ReentrantLock(boolean fair);
//加锁操作,加锁失败会阻塞,底层使用LockSupport.park()阻塞线程
public void lock();
//尝试加锁操作,不会阻塞,返回成功失败
public boolean tryLock();
//允许在等待时由其它线程调用等待线程的Thread.interrupt方法来中断等待线程的等待
//而直接返回,这时不用获取锁,而会抛出一个InterruptedException
public void lockInterruptibly() throws InterruptedException;
//解锁操作,底层使用 LockSupport.unpark()唤醒park()线程
public void unlock() ;
AQS基本结构
//AQS内部维护双向队列的基本结构
public abstract class AbstractQueuedSynchronizer{
//队列的头节点
private transient volatile Node head;
//队列的尾节点
private transient volatile Node tail;
//队列的状态
//State == 0 表示没有线程拿到锁
//State == 1 表示某个线程拿到锁
//State > 1 表示可重入锁,某个上多个锁
private volatile int state;
static final class Node {
//上一个Node
volatile Node prev;
//下一个Node
volatile Node next;
//Node的线程
volatile Thread thread;
//Node中维护的下一个等待Node
Node nextWaiter;
//等待状态
volatile int waitStatus;
}
}
大体如下图:
此处先说明下,aqs的队首元素不参与排队,对比火车站排队买票,排在首位的售票员正在给他处理业务,首位的下一位才是排队的第一位。后续源码会给出分析验证此处观点。
ReentrantLock公平锁加锁操作源码解析
lock unlock简单使用
//初始化ReentrantLock,此处使用公平锁
Lock lock = new ReentrantLock(true);
try{
//加锁操作
lock.lock();
}finally {
//解锁操作
lock.unlock();
}
FairSync.lock公平锁上锁操作
现在开始我们ReentrantLock公平锁的加锁源码解析
//FairSync类lock方法
final void lock() {
//调用AbstractQueuedSynchronizer类acquire方法
//arg写死1
acquire(1);
}
acquire获取锁
public final void acquire(int arg) {
//tryAcquire 尝试获取锁
//当tryAcquire返回false,表示获取锁失败
//会尝试添加到队列中acquireQueued
//addWaiter往aqs队列中添加Node
//acquireQueued会尝试再次获取,因为过程中持有锁的线程可能会释放锁
//最后获取不到会park阻塞线程
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//当前线程中断操作
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
tryAcquire 尝试获取锁
第一个重点来,tryAcquire 方法
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取锁状态
//State == 0 表示没有线程拿到锁
//State == 1 表示某个线程拿到锁
//State > 1 表示可重入锁,某个上多个锁
int c = getState();
if (c == 0) {
//hasQueuedPredecessors是区别于公平锁和非公平锁的本质区别
//非公平锁不会调用此方法,锁空闲时线程一起竞争
//公平锁调用此方法,判断是否需要排队
// 返回false表示不需排队,cas竞争锁
// 返回true表示需要排队,走后续逻辑
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//cas竞争,成功后设置aqs持有锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//此时c > 0表示锁已被某线程持有
//判断持有锁的线程是否为当前线程,true表示可重入
//state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//其他情况返回false
return false;
}
先看下hasQueuedPredecessors方法,很简短但很强悍
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//未初始化时h t都为空,h != t返回false,不需要排队
//队列有值时,队列只有一个节点,首尾相同,false,不需要排队
// 队列有多个节点,首尾不同,返回true,继续后续 && 判断
// h.next == null true表示队列只有一个节点,方法返回true
// false 将头节点的下一个节点赋值给s,判断s是否等于当前线程
// s == 当前线程 表示重入
// s != 当前线程 表示重入
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
总结下3种情况不需排队
1、aqs队列未初始化,h t都为null
2、aqs队列只存在一个元素,执行到最后一个时会存在一个元素
3、第二个节点=当前线程,表示可重入
此处有个问题,为何判断第二个节点等于当前线程而非队首节点?
aqs认为队列第一次不需要排队,要么是拿到锁的线程,要么虚拟的线程。
假如现在有t1 t2两个线程,队列没有初始化时,t1进来 h != t 直接返回false,拿到锁,没有初始化队列。假设t1拿锁时,t2进来,初始化队列,此刻t1持有锁,队列首位默认虚拟节点(aqs的head指向虚拟节点,thread = null),next指向t2。等t1释放锁,t2拿到锁,则t2为对首元素,aqs的head指向t2的Node,t2的Node的thread设为null。所以队首默认为持有当前锁的线程,不参与排队,且thread永远==null。
addWaiter向aqs队列添加原始
private Node addWaiter(Node mode) {
//封装Node
Node node = new Node(Thread.currentThread(), mode);
//队列尾节点赋值给pred
Node pred = tail;
if (pred != null) {
//添加操作,因为aqs是双向队列,添加操作只是维护pred和next指向
//尾节点不为空,赋值给node节点的上一个
node.prev = pred;
//cas将node节点写入aqs内存中,成功把node赋值给之前的尾节点next
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//初始化队列,添加Node
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//尾节点为空需初始化
if (t == null) {
//cas设置头节点,自璇成功进入else,
//注意此处为new node,表示设置头节点是虚拟节点
if (compareAndSetHead(new Node()))
//tail和head都为虚拟节点
tail = head;
} else {
//维护队列,排在队列对尾后一个
node.prev = t;
//继续自璇cas,成功返回
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq执行步骤:
1、首次循环,判断队列尾节点是否为null,是null则初始化队列
2、设置头节点为new Node(),即虚拟Node放入
3、cas设置头节点成功,将头节点赋值给尾节点,即头尾都为虚拟Node
4、再次循环,由于此次队列尾节点不为null,排队node的上一个节点指向虚拟Node
5、cas设置虚拟Node的下一个节点指向排队node,成功返回
acquireQueued获得队列
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//自旋
for (;;) {
final Node p = node.predecessor();
//上个节点为首节点,表示当前node为排队中的第一个节点,尝试获取锁
//获取成功设置node为首节点,
//node.thread = null;
//node.prev = null;
if (p == head && tryAcquire(arg)) {
setHead(node);
//之前队首p.next=null,p无GC ROOT关联,gc可回收
p.next = null;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //park线程
interrupted = true;
}
} finally {
//失败时取消队列
//node.waitStatus = Node.CANCELLED;
//主要讲node的waitStatus 设为 1 ,表示取消状态
//解锁时不会unpark waitStatus >0的队列原始
if (failed)
cancelAcquire(node);
}
}
acquireQueued执行过程:
1、当前node上一个节点,如果是队首,表示当前node是排队中的第一个节点
2、则去尝试获取锁,若获取成功设置当前node为队首,之前的队首移除队列执行,可GC
3、不是排队中的第一个节点或者获取锁失败,进去shouldParkAfterFailedAcquire方法
4、所有node初始化时ws 都为0,进去会cas设置上一node的ws的状态为-1
5、再次循环走 1 2 3 操作
6、若再次进去shouldParkAfterFailedAcquire方法,上一node的ws的状态已为-1,返回true
7、则会进去parkAndCheckInterrupt方法,这方法比较简单,调用LockSupport.park()
8、当前线程阻塞,直至unlock()调用LockSupport.unpark(当前线程)才会继续后续执行
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//取当前node上一节点的ws
//aqs中上一个node管理下一个node的ws状态
//因为当前node后续会park阻塞操作,不能有任何操作
//解锁过程需要下一个node的ws状态
//类比睡着的人不知道自己睡着没有,需要别人管理他的睡眠状态
//别人看这个人睡着了,关上门,贴个睡着牌子
int ws = pred.waitStatus;
//SIGNAL为-1
//1、判断ws为-1 返回 true,pred初始化时为0,首次不会进来
//4、自旋继续到此,上次已设置为-1,会进来,返回true
if (ws == Node.SIGNAL)
return true;
//2、pred首次为0不会进来
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//3、设置pred.waitStatus为-1
//-1表示线程park,正在睡眠
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
//park阻塞线程
LockSupport.park(this);
//为何需要执行这一段操作很操蛋,看的很模糊,下面结合方法统一说明下
return Thread.interrupted();
}
selfInterrupt操作
解释下selfInterrupt操作,本人看这段操作很迷,我们先看下流程,!tryAcquire(arg)获取锁失败则acquireQueued方法中parkAndCheckInterrupt因为调用了Thread.interrupted()方法,会返回true,所以会执行Thread.currentThread().interrupt()操作。
先说明下Thread.interrupted()方法,用户执行interrupt()操作后,Thread.interrupted()首次执行会返回true,再次执行会返回false,即操作一次使其恢复默认false。
那么为了不改变用户行为,在parkAndCheckInterrupt中因为调用了次Thread.interrupted(),如果返回true表示用户执行过interrupt()操作,所以要在外面还原回来,再次执行下interrupt操作。
是不是很迷,有种和空气斗智斗勇的感觉,你直接不做判断不就啥事没有,因为Thread.interrupted()的状态返回值代码中没有任何用处,很蛋疼?
后来看了lockInterruptibly方法,最后调用doAcquireInterruptibly方法,是不是和acquireQueued很相似,只是这边parkAndCheckInterrupt返回true会抛异常,所以作者应该是复用代码,所以上面迷一般的操作是为了兼容复用,很蛋疼。
private void doAcquireInterruptibly(int arg)
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
ReentrantLock非公平锁加锁操作源码解析
区别于公平锁在于下面两个方法
final void lock() {
//进去lock方法时所有线程去cas竞争锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//竞争失败调用下面的nonfairTryAcquire方法
//acquire中调用nonfairTryAcquire
acquire(1);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
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;
}
ReentrantLock解锁操作源码解析
ReentrantLock.unlock解锁
public void unlock() {
sync.release(1);
}
release释放锁
//释放锁
public final boolean release(int arg) {
//尝试释放操作
if (tryRelease(arg)) {
//释放成功,则判读aqs头节点不为空且waitStatus不为0
//之前lock操作已说明,h.waitStatus表示下一个node的
Node h = head;
//waitStatus = 0表示初始状态,park操作前会修改为 -1,需唤醒
if (h != null && h.waitStatus != 0)
//执行unpark操作,唤醒线程
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease尝试释放锁
protected final boolean tryRelease(int releases) {
//aqs的state - 1
//state = 1 表示当前线程持有锁
//state > 1 表示当前线程重入锁
int c = getState() - releases;
//判断线程是否是否为锁的持有线程,一般都为true
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//c == 0表示已释放锁,线程free空闲,持有锁线程为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//重新设置state值
setState(c);
return free;
}
unparkSuccessor唤醒线程
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//判断下一个node的waitStatus<0 ,cas设为0
//进来此方法一般都为-1
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//取出下一个node,判断不为空或者waitStatus>0
//两者有一个为true表示下一个node要么为空,要么是取消状态,不需唤醒
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
//循环下面的node节点,取出waitStatus<0的node
//表示此node的下一个node为有效需唤醒节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//判断s不为null,则执行unpark唤醒操作
if (s != null)
LockSupport.unpark(s.thread);
}
心得
扒了ReentrantLock和AQS代码,真的佩服Doug Lea,大写的牛逼。
希望我写的内容能对读者有作用,可以对比注释逐行过下代码,多看两遍会有不一样的心得。
当然我写的也不一定都对,只是个人对ReentrantLock和AQS代码的理解,如有错误的地方,麻烦指出。