ReentrantLock的加锁时序图
ReentrantLock类图
分析的几个问题
- ReentrantLock是如何实现加锁的
- 当锁被其他线程占用时当前线程是如何处理的
带着这两个问题分析一下
ReentrantLock加锁过程
ReentrantLock的加锁过程很简单就是采用CAS操作将锁标记设置成有锁状态源码如下
final void lock() {
//当没有线程占用锁资源时直接CAS成功,将获取锁的线程设置成为当前线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//当获取所失败时调用acquire方法将当前线程加入到队列中
acquire(1);
}
所以重点看一下acquire方法是如何操作的
public final void acquire(int arg) {
//这里看的是非公平锁的实现所以我们要分析NonfairSync中的tryAcquire
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire是一种锁的重试机制当tryAcquire返回true的时候线程一样可以获取锁成功那就看一下这个方法的实现
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁的状态
int c = getState();
//如果锁的状态为0,说明之前被占用的锁已经被释放了,同样采用CAS设置锁的状态并且设置
//锁的的持有者为当前线程
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果所依然没有释放,判断锁的持有线程是不是当前线程,如果是就就将当前锁状态加1,获取锁
//成功,通过这一段代码可以分析出RenntrantLock是一个可重入锁
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;
}
那么接下来分析一下如果tryAcquire失败了将会怎么操作,这个就要分析一下acquire(1)方法,这个方法是AbstractQueuedSynchronizer中的
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
如果获取锁失败会先调用addWaiter(Node.EXCLUSIVE), arg)将当前线程加入到队列中,Node.EXCLUSIVE表示独占锁的意思
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;
那么来分析一下addWaiter(Node.EXCLUSIVE), arg)方法的实现
private Node addWaiter(Node mode) {
//首先将当前线程包装成一个Node
Node node = new Node(Thread.currentThread(), mode);
//获取tail tail等待队列的尾部
Node pred = tail;
//如果队列尾部为null则初始化tail和head节点
if (pred != null) {
// 如果当前tail不为null,将node的前置节点设置成tail,并且
// 采用CAS将tail节点设置成node(上面的node)
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果tail为null初始化当tail和head节点
enq(node);
return node;
}
下面分析一下enq方法
private Node enq(final Node node) {
//自旋的方式初始化
for (;;) {
// 获取tail节点
Node t = tail;
// 如果tail节点为null,就将构造一个node节点,并且设置head和tail都为
// 这个新构造的node节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//如果不为null就设置tail节点为当前线程构造的node节点,并且将当前线程的前
//置节点设置成tail
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiter方法会将当前线程的节点返回,那么分析一下acquireQueued(addWaiter(Node.EXCLUSIVE), arg))方法
final boolean acquireQueued(final Node node, int arg) {
// 获取锁是否失败的标志
boolean failed = true;
try {
// 线程中断标志
boolean interrupted = false;
// 自旋获取锁
for (;;) {
// 获取当前节点的前置节点
final Node p = node.predecessor();
// 如果前置节点为head就重试获取锁
if (p == head && tryAcquire(arg)) {
//重试成功就将head(表示当前拥有锁的节点)设置成当前线程node
setHead(node);
p.next = null; // help GC
//获取锁成功
failed = false;
//线程不中断返回
return interrupted;
}
// 获取锁失败后线程是否要挂起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//在这个地方线程会阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
接下来分析一下shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
//如果当前节点的前置节点为SIGNAL那么当前线程就是要挂起的状态
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 如果前置节点大于零(为取消状态CANCELLED)
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//前置任务已取消,跳过前置任务并指示重试 (将队列中取消的节点删除掉)
/**
*node.prev=pred
*pred=pred.prev
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//将前置节点设置为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
那么就可以得到档线程第一次进来的时候会把前置节点设置成SIGNAL状态,然后会调用parkAndCheckInterrupt())将当前线程挂起,到这里获取锁的过程已经解释完了,那么被挂起的线程什么时候会被唤醒,就是当占有锁的线程将锁释放之后,那么就来分析一下释放锁的代码
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;
}
分析tryRelease方法
protected final boolean tryRelease(int releases) {
//锁标记-1
int c = getState() - releases;
//如果释放锁的线程不是占有锁的线程就会抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//如果c的值为0表示锁释放成功
if (c == 0) {
free = true;
//设置占有锁的线程为null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
分析unparkSuccessor(h)方法
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
//获取头结点的状态如果小于0就设置成0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
//获取当前节点的下一个节点
Node s = node.next;
//如果为null或者是取消状态
if (s == null || s.waitStatus > 0) {
s = null;
//从后往前遍历链表,得到最前面一个状态小于0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//唤醒s节点的线程,这个线程又开始在它挂起的地方开始自旋获取锁
if (s != null)
LockSupport.unpark(s.thread);
}
那么这个地方为什么要有从后往前遍历?
答:我们看一下addWaiter方法,如果在设置next节点的时候cpu时间片到期,这个时候没有设置成功,而恰巧遍历到这个地方,就会得到null节点那么就不会唤醒当前线程,而这时候该节点又不是head节点,所以在acquireQueued方法中就不会tryAcquire,那么这个线程就会被挂起,而此时释放锁的线程已经释放结束了,如果没有新的线程加入进来,就不会再有释放锁的动作,这样就会导致该节点以及后面的节点全部处于挂起状态而无法被唤醒,进而造成线程死锁,而后置节点一定会有前置节点,因而不会出现这种状况
回过头来看acquireQueued方法,重点在注释部分
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//这个时候获取锁一定会成功,因为在被唤醒的时候已经释放锁成功了
if (p == head && tryAcquire(arg)) {
//这个时候就会把head节点设置成当前获取锁的这个节点,正对应head节点
//表示锁占有的节点
/**
*private void setHead(Node node) {
* head = node;
* 线程设置为null
* node.thread = null;
* 前置节点设置成null
* node.prev = null;
*}
*/
setHead(node);
//将head节点释放掉
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
总结
只是粗略的讲解一下ReentrantLock中锁的获取方式,后面会继续研究,慢慢补全