ReentrantLock源码分析
1.1、构造与使用
代码示例1:
ReentrantLock lock = new ReentrantLock();
try {
lock.lock();
// do somethings
}finally {
if(lock.isLocked()) {
lock.unlock();
}
}
上面代码,共三句关键语句:
1、构造对象:new ReentrantLock();
2、获得锁:lock();
3、释放锁:unlock();
下面逐一解析。
1.2、解析 new ReentrantLock()语句
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();
}
从源码可看出,构造方法ReentrantLock()只完成了一件事,就是创建一个NonfairSync()对象,并赋值给sync变量。
sync是一个Sync类型变量,Sync类是ReentrantLock的抽象静态内部类,它有两个实现类,即:FairSync(公平锁)和NonfairSync(不公平锁)。
1.3、解析代码示例1中lock.lock()语句
public void lock() {
sync.lock();
}
从源码可看出,ReentrantLock.lock()方法的操作相当简洁明了,那就是调用sync对象的sync.lock()方法,通过1.1.1,我们知道sync此时是一个NonfairSync对象,那么真正的执行就要看NonfairSync.lock()方法,如下:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
lock方法语义有三步:
1、执行:compareAndSetState(0, 1);
2、成功则执行:setExclusiveOwnerThread(Thread.currentThread());
3、失败成执行:acquire(1);
解析1
先说结论,当前线程尝试获取锁,成功则返回true,失败返回false。 compareAndSet 简称CAS,是保证原子操作的一种机制,一般多用于实现乐观锁。具体实现是:判断指定的存储区域的值是否为0,如果是,则将该存储区域的值设置为1并且返回true,否则返回false。
源码如下:
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
该方法直接调用了sum.misc.unsafe.compareAndSwapInt方法,这里不再展开,因为它是一个native方法,大概了解到它的代码语义为:在this[stateOffset]这个内存区域,如果值为0(这是初始值),则把它设置为1,并且返回true,否则啥也不做,然后返回false。注意,上述描述的过程是原子操作。
思考:compareAndSetState(0, 1)操作为什么能在多线程环境下获取锁?
解析2
解析:该方法很简单,源码如下:
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
只是简单地赋值语句。此时sync.lock()已执行完毕,没有任何阻塞,代码回到示例1 //do somethings 片断,继续完成相关业务。
那么就这么简单?
如果你觉得简单,只能说明你是幸运线程。假如两个线程同时执行一个同一个ReentrantLock的lock()方法,能直接进来的线程也只有一个而已,其余的都在解析1过程中,就已经失败了,流程都转到了解析3,那么现在看看解析3都干了些啥吧。
解析3
失败者的命运,直接看看看它们都经历了一些什么事吧~
// arg为1
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
关键在于tryAcquire(1)和acquireQueued(addWaiter(Node.EXCLUSIVE), 1)都干了些什么事儿。
1、tryAcquire是AbstractQueuedSynchronizer中定义的方法,由于我们sync的实例是NonfairSync,所以我们需要看NonfairSync对于tryAcquire的实现。NonfairSync.tryAcquire(1)源码如下:
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) {
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;
}
nonfairTryAcquire(1)中,调用了nonfairTryAcquire(1)方法,我们将注意力移到此方法,分析如下:
1、该方法有两个分支, 当状态为0时,进行CAS(上文已分析过该方法)操作,尝试获取锁,成功则执行setExclusiveOwnerThread方法(上文已分析过该方法),并返回true。失败则返回false。
2、另一个分支,判断当前线程是否为独占锁的拥有者(即执行了setExclusiveOwnerThread()),如果当前正在执行的线程,将状态加1,并执行setState,返回true。
getState()和setState()源码如下:
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
state初始为0。
我们回来看tryAcquire()整个代码含义是用于判断线程线程是否获得了锁,有锁则返回true,未获取到锁则返回false。
我们继续回到调用tryAcquire的地方:解析3
tryAcquire(1)如果返回true,表示锁在当前线程,可以继续往下执行,否则执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),源码如下:
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;
}
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)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//当前线程因为某种原因未获取到锁,并且又被唤醒,则执行下列方法。
if (failed)
cancelAcquire(node);
}
}
先看看addWaiter()方法,以当前线程为参数,新建了一个Node结点,写入链表尾部(同样使用了CAS来保证线程安全),并返回。
再看下acquireQueued()方法,for()是一个无限循环,并且退出条件是"p == head && tryAcquire(arg)",即node为顶部结点,且获取锁成功。
如果不为顶部结点并且锁获取失败,则进入shouldParkAfterFailedAcquire()方法,源码如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//当前结点的前置结点状态,如果node第一次进入shouldParkAfterFailedAcquire方法, ws为0
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果node是第二次进来,则返回true, 表示node需要休眠
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//将 node的前置结点pred的status设置Node.SIGNAL,返回false(表示node结点不需要休眠)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
由shouldParkAfterFailedAcquire判断当前线程是否需要休眠。
parkAndCheckInterrupt()方法使当前线程休眠,并且返回当前线程的中断状态,如果当前线程已中断(注意什么情况下线程会被中断?一般而已是由外部不可抗拒因素导致线程中断标记被置为true,而LockSupport.park(this)方法并不会使线程Interrupt为true),则将interrupted设置为true。
我们再回头看看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)整个逻辑:
1、调用addWaiter方法,使用当前线程作为参数,生成一个node结点,并加入到等待队列。
2、调用acquireQueued方法,如果当前node在队列首部,则尝试获取锁,否则在自旋一次后就休眠,再被唤醒时,会检查当前线程的中断状态,如果当前线程已中断或被异常中断,并且未获得到锁,则会执行cancelAcquire(node)。源码如下:
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
//丢弃队列中为CANCELLED状态的结点
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
//将node结点的前置结点设置为尾结点
if (node == tail && compareAndSetTail(node, pred)) {
//将前置结点的后置结点设置为null
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
//node出列
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
cancelAcquire()方法主要目的是释放status为CANCELLED的结点,可以看到,如果node的前置结点为头结点,或者pred的status不为SIGNAL,则会执行unparkSuccessor(node)操作,源码如下:
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//如果状态为SIGNAL、CONDITION、PROPAGATE,则再次初始化为0
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;
}
//唤醒后置结点的node
if (s != null)
LockSupport.unpark(s.thread);
}
unparkSuccessor主要的功能就是唤醒node后置结点的线程。
总结
多个线程调用同一个ReentrantLock对象的lock()方法时, 只有一个线程能获取到锁,其余线程会再次尝试获取锁,失败后,会被添加到一个FIFO队列中,在该列队中,会从头部结点开始尝试获取锁,其余结点则在循环一次之后,便会休眠,等待唤醒。
在头部节点获取锁成功后,如果该线程的中断标记被置为了true,则会调用selfInterrupt()方法,设置当前线程的中断标记为true,然后继续执行该线程业务。
假如在获取锁的过程中,因为异常退出则会执行cancelAcquire()方法,用于清除为CANCELLED状态的结点。