ReentrantLock原理剖析
说到ReentrantLock,我们都知道他是基于AQS(AbstractQueuedSynchronizer)的可重入互斥锁。
我们想要剖析ReentrantLock,就不得不先从AbstractQueuedSynchronizer开始。
剖析AbstractQueuedSynchronizer
AbstractQueuedSynchronizer有一个内部类Node,底层维护了一个双向链表而形成一个队列。也就是我们的AQS队列。当我们获取锁失败后的线程会被放到这个队列中,等待其他线程释放lock的时候被唤醒。
ReentrantLock整个加锁过程就是围绕着state属性来进行的。当state字段大于0时,代表当前资源被锁住,并且state的值代表当前持有锁线程的重入次数,当state为0时,队列中的线程就可以通过一系列的CAS操作来争抢这个锁。
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
//等待状态
volatile int waitStatus;
//前一个结点
volatile Node prev;
//后一个节点
volatile Node next;
//当前节点线程
volatile Thread thread;
//头指针
private transient volatile Node head;
//尾指针
private transient volatile Node tail;
//重入次数
private volatile int state;
}
}
Sync
Sync是ReentrantLock的抽象内部类,继承了AQS,ReentrantLock的主要功能全靠这个内部类Sync。他定义了获取锁和释放锁的通用模板。并且定义了非公平获取锁方法。
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
//非公平锁获取方法
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;
}
}
Sync有两个亲儿子,NoFairSync(非公平锁)和FairSync(公平锁),ReentrantLock的主要功能就是通过这两个实现类来具体实现。
NoFairSync(非公平锁)
获取锁流程
-
尝试使用CAS加锁
final void lock() { //尝试使用CAS加锁 if (compareAndSetState(0, 1)) //加锁成功,将当前资源锁线程设置为当前线程 setExclusiveOwnerThread(Thread.currentThread()); else //加锁失败,走父类Sync定义的加锁流程 acquire(1); }
-
若CAS加锁失败,走Sync定义的加锁流程
public final void acquire(int arg) { //尝试加锁 if (!tryAcquire(arg) && //加锁失败,将当前线程放到AQS队列尾部 //并判断当前线程是不是排在AQS队列第一个,若是,重新尝试获取锁,若不是,则找到前一个可用线程,并挂起当前线程 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
-
tryAcquire非公平加锁,使用的是父类Sync定义好的nonfairTryAcquire()方法
protected final boolean tryAcquire(int acquires) { //父类Sync定义好的nonfairTryAcquire() return nonfairTryAcquire(acquires); } protected final boolean nonfairTryAcquire(int acquires) { //获取当前线程 final Thread current = Thread.currentThread(); //获取state值 int c = getState(); //如果state为0,代表可以上锁 if (c == 0) { //使用CAS方式将state从0设置为1 if (compareAndSetState(0, acquires)) { //若CAS加锁成功,则将持有锁资源线程设置为当前线程 setExclusiveOwnerThread(current); //返回加锁成功 return true; } } //如果加锁失败,判断持有锁资源线程是不是当前线程 //如果是,则开始锁重入操作 //如果不是,则放弃重入 else if (current == getExclusiveOwnerThread()) { //state + 1 int nextc = c + acquires; //超出重入次数限制,抛出异常 if (nextc < 0) throw new Error("Maximum lock count exceeded"); //设置state = state + 1 setState(nextc); //返回加锁成功 return true; } //否则,返回加锁失败 return false; }
-
addWaiter(Node.EXCLUSIVE) 若获取锁失败,将当前线程添加到AQS队尾
//加锁失败,将当前线程加到AQS队列尾部 private Node addWaiter(Node mode) { Node node = new Node(Thread.currentThread(), mode); //获取AQS队尾节点 Node pred = tail; if (pred != null) { //将当前线程的前一个节点指向原来的尾部 node.prev = pred; //使用CAS方式将原来的尾节点的指针指向刚刚这个新的线程 if (compareAndSetTail(pred, node)) { pred.next = node; return node; } } //若使用CAS将当前线程设置到AQS尾部失败,则执行此方法 enq(node); return node; } //使用死循环,直到将当前线程设置到队尾成功为止,才跳出循环 private Node enq(final Node node) { for (;;) { //获取队尾指针 Node t = tail; //若队尾指针为null,则证明AQS是一个空队列,需要被初始化 if (t == null) { //创建一个node并设置为头指针 if (compareAndSetHead(new Node())) //队尾指针指向头指针 tail = head; } else {//若队尾指针不为null,将当前线程设置到队尾 node.prev = t; if (compareAndSetTail(t, node)) { t.next = node; return t; } } } }
-
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) 判断当前线程是否排在AQS前面,若是,则重新尝试获取锁,若不是,则找到AQS前面的等待线程,将自己挂起
//判断当前线程是不是排在AQS队列的第一个 //若是,则重新获取锁 //若不是,则将当前线程挂起 final boolean acquireQueued(final Node node, int arg) { //获取锁失败flag boolean failed = true; try { //中断flag boolean interrupted = false; //死循环:线程若是AQS前面的节点,则可以去尝试获取锁,若不是,则从后往前直到找到一个活跃的线程节点,然后将自己挂起 for (;;) { //获取当前线程的前一个节点 final Node p = node.predecessor(); //若当前线程的前一个节点是head,则证明当前线程在AQS头部,重新尝试获取锁 if (p == head && tryAcquire(arg)) { //获取锁成功,下一步操作... //将当前线程设置为head节点 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; //若前一个线程也是处于等待状态,则返回true,可以将当前线程挂起 if (ws == Node.SIGNAL) return true; //如果前一个线程是取消状态,那么久一直向前遍历,直到获取到状态小于0的线程 if (ws > 0) { do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //将找到的这个线程状态设置为等待(-1) compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; } //调用park()方法将线程挂起 private final boolean parkAndCheckInterrupt() { LockSupport.park(this); return Thread.interrupted(); }
FairSync(公平锁)
公平锁和非公平锁的区别:
-
非公平锁刚进入lock方法就会使用CAS尝试获取锁,获取锁失败才会调用acquire()方法,而公平锁是直接调用acquire()方法
//非公平锁 final void lock() { //默认先使用一次CAS if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } //公平锁 final void lock() { //直接调用获取锁通用方法 acquire(1); }
-
非公平锁在acquire()获取锁方法中没有判断AQS队列是否存在排队现象,不管当前线程是否存在AQS队列中都可以去争抢锁。而公平锁会先调用hasQueuedPredecessors()方法查看队列是否存在排队,若有排队现象,会将当前线程存入AQS队列尾部,然后才去尝试获取锁
//非公平锁 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //获取锁方法中没有判断AQS队列是否存在排队现象,不管当前线程是否存在AQS队列中都可以去争抢锁 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; } //公平锁 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //先调用hasQueuedPredecessors()方法查看队列是否存在排队 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } }
解锁过程 unlock()
公平锁和非公平锁解锁都是走的一样的代码,都是使用AQS的release()方法进行解锁。
public void unlock() {
sync.release(1);
}
release()过程
public final boolean release(int arg) {
//尝试解锁
if (tryRelease(arg)) {
Node h = head;
//解锁成功,若头节点的状态 !=0 说明AQS还存在排队线程 需要将后面的线程唤醒
if (h != null && h.waitStatus != 0)
//从后往前找到离头节点最近的线程进行唤醒,准备抢锁
unparkSuccessor(h);
return true;
}
return false;
}
-
释放锁 tryRelease()
//尝试释放锁 protected final boolean tryRelease(int releases) { //state - 1 int c = getState() - releases; //若当前线程 != 持有锁线程 抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); //释放资源成功标记falg boolean free = false; //c == 0 代表state == 0 代表释放资源成功 if (c == 0) { free = true; //持有锁线程设置为null setExclusiveOwnerThread(null); } //state = state - 1 等待下次重入-1 setState(c); return free; }
-
释放锁成功 唤醒AQS中的其他等待线程
private void unparkSuccessor(Node node) { //获取头节点的状态 int ws = node.waitStatus; //如果头节点状态 > 0 那么将头节点的状态设置为0 if (ws < 0) compareAndSetWaitStatus(node, ws, 0); //获取头节点的下一个节点 Node s = node.next; //找到离头节点最近的线程进行唤醒操作 //如果下一个节点的状态 > 0 说明下一个线程已经放弃获取锁 if (s == null || s.waitStatus > 0) { s = null; //从AQS尾部从后往前找,找到离找到离头节点最近的线程 for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } //进行唤醒操作 if (s != null) LockSupport.unpark(s.thread); }