ReentrantLock是一个可重入的互斥锁,基于AQS实现,它具有与使用 synchronized 方法和语句相同的一些基本行为和语义,但功能更强大。
lock和unlock
ReentrantLock 中进行同步操作都是从lock方法开始。lock获取锁,进行一系列的业务操作,结束后使用unlock释放锁。
private final ReentrantLock lock = new ReentrantLock();
public void sync(){
lock.lock();
try {
// ... method body
} finally {
lock.unlock()
}
}
lock
ReentrantLock 中lock的实现是通过调用AQS的AbstractQueuedSynchronizer#acquire方法实现。
public final void acquire(int arg) {
//尝试获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
根据之前介绍的模板方法模式,对于锁的获取tryAcquire是在ReentrantLock中实现的。而非公平锁中的实际实现方法为nonfairTryAcquire。
ReentrantLock#nonfairTryAcquire
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;
}
在获取锁的逻辑中首先是尝试以cas方式获取锁,如果获取失败则表示锁已经被线程持有。
再判断持有该锁的线程是否为当前线程,如果是当前线程就将state的值加1,在释放锁是也需要释放多次。这就是可重入锁的实现。
如果持有锁的线程并非当前线程则这次加锁失败,返回false。加锁失败后将调用AbstractQueuedSynchronizer#acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。
首先会调用addWaiter方法将该线程入队。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
mode是指以何种模式的节点入队,这里传入的是Node.EXCLUSIVE(独占锁)。首先将当前线程包装为node节点。然后判断等待队列的尾节点是否为空,如果不为空则通过cas的方式将当前节点接在队尾。如果tail为空则执行enq方法。
AbstractQueuedSynchronizer#enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
enq方法通过for(;;)无限循环的方式将node节点设置到等待队列的队尾(队列为空时head和tail都指向当前节点)。
综上可知addWaiter方法的作用是将竞争锁失败的节点放到等待队列的队尾。
等待队列中的节点也并不是什么都不做,这些节点也会不断的尝试获取锁,逻辑在acquireQueued中实现。
AbstractQueuedSynchronizer#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)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}