ReentrantLock 加解锁源代码分析
ReentrantLock是java同步JUC的一个重要功能,下面我们就从源代码入手,详细分析它的加解锁过程。
ReentrantLock.lock() 加锁
public void lock() {
// 其中sync为 Sync -> AbstractQueuedSynchronizer(为了方便下文简称AQS),说明Sync是基于AQS实现的。
// 对于Sync的实现又有NonfairSync和FairSync
sync.lock();
}
NonfairSync 加锁流程
/**
* 执行加锁
* 首先:尝试修改state状态,如果修改成功表示获取锁,将独占线程修改为当前线程
* 其次:如果修改失败,则进入正常的加锁流程
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
/**
* 以独占的方式获取锁,并且忽略中断。
*
*/
public final void acquire(int arg) {
// 如果没有获取成功,则将当前线程添加到队列尾部然后将当前线程中断
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//没有线程持有锁
if (c == 0) {
//以CAS的方式去获取锁,如果获得成功,则将独占线程设置为当前线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//是否可重入
else if (current == getExclusiveOwnerThread()) {
// 如果是当前线程持有锁,那么state就是当前线程获取锁的次数,每当获取一次锁,状态计数器加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 这里没有并发,直接set 计数器的值
setState(nextc);
return true;
}
return false;
}
/**
* 根据给出的模式为当前线程创建入队节点
* 有独占和共享两种模式,对于ReentrantLock来说,是独占模式
*/
private Node addWaiter(Node mode) {
// 为当前线程创建入队节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
// 如果队列不为空,将节点插入到队尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 初始化队列并将当前线程的节点插入到队尾
enq(node);
return node;
}
/**
* 初始化队列并将node插入到队尾
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 如果队列为空,新建一个空节点来初始化队列
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 队列初始化完成后将node插入到队尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//判断node是不是第二个节点,如果是,则尝试获取锁,如果获取成功则将node置为首节点,返回false
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* 从方法名上理解,如果锁获取失败了应该阻塞当前线程
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //Node.SIGNAL = -1;
/*
* 如果为node的前继节点为SIGNAL,那么当前继节点释放锁后,会唤醒后继(当前)节点,那么node可以安全阻塞
*/
return true;
if (ws > 0) { // 前继节点取消
/*
* 前继节点被取消,则跳过前继节点重试
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 否则把前继节点的状态设置SIGNAL,然后下一次进来是直接返回true。利用LockSupport.park()方法将线程阻塞然后再中断。
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
// 将当前线程阻塞
LockSupport.park(this);
return Thread.interrupted(); //返回线程是否中断
}
FairSync 公平锁
公平锁的加锁流程就是非公平锁开始获取锁失败的,进入正常流程的环节,具体加锁流程参考非公平锁的部分。
final void lock() {
acquire(1);
}
ReentrantLock.unlock() 解锁
通过上面的加锁流程,我们可以知道,无论是公平锁还是非公平锁,它们获取锁的过程其实就是当前线程将state的状态由0变为1(同一个线程重入就是将state状态累加),如果当前线程没有获取锁就将给当前线程创建一个node节点添加到等待队列的末尾,并且将当前线程阻塞,直到前一个线程释放锁并且唤醒当前线程的过程。由此推断解锁的过程无非也就是将state状态由1到0(同一线程可重入的时候就是将正整数递减到0),并且唤醒后继线程的过程。以下是源代码分析:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) { // 如果释放成功
Node h = head; // 释放锁后,如果还有线程在等待锁,则唤醒首节点(首节点是个空节点)后面的一个节点
if (h != null && h.waitStatus != 0) //
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases; //如果当前线程没有重入,那么这里的getState应该等于1
if (Thread.currentThread() != getExclusiveOwnerThread()) //只有拥有锁的线程才能释放锁
throw new IllegalMonitorStateException();
boolean free = false;
//如果没有重入,一次释放
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//如果重入了,那么state递减,
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//如果还有线程在等待锁,那么首节点的waitStatus=-1
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0); // 这里先将它置为默认值
Node s = node.next; //获得第二个节点
if (s == null || s.waitStatus > 0) { //第二个节点被取消
s = null;
//从尾部向前拿到第一个没有被取消的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread); //唤醒没有被取消的节点的线程
}
相比加锁流程,解锁流程还是很easy的。至此,ReentrantLock的加解锁的流程就分析完了,顺便提下,ReentrantLock默认为非公平锁。