ReentrantLock
同时支持公平锁和非公平锁以及可重入是ReentrantLock的两大重要特点。那么,你知道它的实现原理吗?
了解类关系
ReentrantLock实现了Lock接口。
public class ReentrantLock implements Lock, java.io.Serializable
Sync是ReentrantLock内部的一个抽象类,它继承了AQS,也就是抽象队列同步器。
abstract static class Sync extends AbstractQueuedSynchronizer
ReentrantLock内部还有两个抽象类,分别是FairSync和NonfairSync,意思是公平同步器和非公平同步器,它们都是Sync接口的实现类。
static final class FairSync extends Sync
static final class NonfairSync extends Sync
了解重要概念
state
state是AQS中很重要的概念,是一个由volatile修饰的int变量,用于记录同步状态。
- 当线程第一次调用lock方法时,state会从0变为1,如果这个线程再次调用lock方法时,state会进行自增;
- 当线程调用unLock方法时,state自减;
private volatile int state;
需要注意的是,重入多少次就必须释放多少次,因为只有state为0的时候,才会释放资源。
public void unlock() {
//释放资源,state-1
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) {
//state-1
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//只有state为0的时候,才会释放资源
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
CLH队列
CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败,AQS就会将当前线程以及等待状态等信息构造成一个Node并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。 在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next)。
公平锁/非公平锁的实现
到底支持公平锁还是支持非公平锁呢?ReentrantLock通过以下两个构造方法来供我们选择。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public ReentrantLock() {
sync = new NonfairSync();
}
非公平锁的实现
NonfairSync#lock
当state为0时,如果同时有多个线程来调用lock方法,那么只会有一个线程通过CAS获得锁,同时state+1,并设置该线程为独占锁线程;后续所有线程在该线程释放锁之前调用lock都会走acquire的逻辑来竞争锁。
如果线程A进来获取锁时恰好原来的独占锁线程释放锁,那么线程A会在同步队列中所有等待获取锁的线程之前抢先获取锁,也就是说所有在同步队列中的尚未被取消获取锁的线程是绝对保证串行获取锁的,但其它新来的线程可能会抢先获取锁。这便是“非公平”的含义。
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
AQS#acquire
tryAcquire
方法仍然尝试获取锁,成功返回false,如果没有成功, 就将此线程包装成Node加入同步队列尾部。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
NonfairSync#tryAcquire
//调用nonfairTryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
NonfairSync#nonfairTryAcquire
//仍然尝试去获取锁
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//如果state为0,即无锁状态
int c = getState();
if (c == 0) {
///如果满足条件CAS获取同步状态,则设置当前独占线程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为0,则判断当前线程是否为独占线程(重入锁逻辑)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//state+1
setState(nextc);
return true;
}
return false;
}
AQS#addWaiter
private Node addWaiter(Node mode) {
//nextWaiter被赋予为EXCLUSIVE,表示独占模式
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//如果pred不为空,也就是队列不为空
if (pred != null) {
//将当前节点设为新的tail
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
// Used by addWaiter
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
AQS#enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
//如果队列为空
if (t == null) { // Must initialize
//初始化头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
//通过CAS入队
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
//入队成功,跳出循环
return t;
}
}
}
}
AQS#acquireQueued
//入队成功
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//又又又尝试获取锁~
final Node p = node.predecessor();
//如果当前刚入队节点的前置节点是头节点,那么有必要再次尝试获取锁,因为head很可能是刚初始化的head,或者也可能head马上就会释放锁
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);
}
}
AQS#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.
*/
//那么当前置结点执行完成后是可以唤醒后续结点的,此时可以安全的挂起当前结点,因此返回true
return true;
// ws>0,说明前置结点是被自己取消获取同步的结点
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
//通过do while循环向往头结点方向找waitStatus < 0的节点
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,但还没有进行park,所以调用者必须确保在挂起之前不能获取同步状态,并强行设置前置结点为SIGNAL。之所以CAS设置是因为,pred线程也会操作cancelAcquire来取消自己,从而使node线程对pred的操作产生竞争条件。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
AQS#cancelAcquire
private void cancelAcquire(Node node) {
// 如果节点为空
if (node == null)
return;
node.thread = null;
// 跳过状态为取消的前置节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If如果node是tail,更新tail为pred,并使pred.next指向null,也就是移除当前node
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
//如果node既不是tail,又不是head的后继节点
if (pred != head &&
//如果前置结点状态是SIGNAL或者通过CAS设为SIGNAL 那么接下来就会唤醒下一个结点
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
//使已经找到的有效前置结点指向node的下一结点吗,即移除当前node
compareAndSetNext(pred, predNext, next);
} else {
//如果node是head的后继节点,则直接唤醒node的后继节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
公平锁的实现
相较非公平锁,公平锁的lock方法没有了插队的概念, 所有来的线程必须扔到队列尾部, acquire方法也会像非公平锁一样首先调用tryAcquire尝试获取锁,但只有队列为空或本身就是头节点,才可能成功。
FairSync#lock
final void lock() {
acquire(1);
}
AQS#acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//中断当前线程
selfInterrupt();
}
FairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//如果state为0,即无锁状态
if (c == 0) {
//当前线程只有在队列为空或者是队列头部的时候,才能获取资源,否则会被加入到阻塞队列中
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//如果满足条件CAS获取同步状态,则设置当前独占线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果state不为0,则判断当前线程是否为独占线程(重入锁逻辑)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//state自增
setState(nextc);
return true;
}
return false;
}
}
AQS#hasQueuedPredecessors
//如果当前线程之前有一个排队线程,则为 true;如果当前线程位于队列的头部或队列为空,则为 false
public final boolean hasQueuedPredecessors() {
// Read fields in reverse initialization order
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}