ReentrantLock
AQS
AbstractQueuedSynchronizer 抽象队列同步器 内部维护两个队列/链表:
- 同步队列(双向链表)
- 条件队列 (单向链表)
条件队列中的线程必须进入同步队列才能得到资源执行,类似synchronized的wait后进入waitset,需要notify进入cxq/EntryList才能抢到资源
默认初始化 为非公平锁
public ReentrantLock() {
//非公平锁
sync = new NonfairSync();
}
Node的几个状态
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
看注释也差不多理解了
主要记住0就是在同步等待 -1代表后面有节点,-2代表自身调用了await()方法后进入条件队列
非公平锁
加锁逻辑
//NonfairSync重写方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//获取锁失败
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//acquire方法
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
非公平锁下尝试获取锁方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
//exclusiveOwnerThread 标记当前线程持有锁
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
//锁重入
int nextc = c + acquires;
//标记次数 state > 1代表持有锁 state-1代表重入次数
setState(nextc);
return true;
}
return false;
}
添加Node节点至队列,保证入队列尾部
private Node addWaiter(Node mode) {
//包装线程为Node节点
Node node = new Node(Thread.currentThread(), mode);
//尾部节点不为空 尝试cas添加至尾端
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//cas添加失败 或者尾部节点为空 ,死循环尝试 直至成功
enq(node);
return node;
}
//返回原队尾节点
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
//尾部节点为空 初始化一个空节点 作为head
if (compareAndSetHead(new Node()))
tail = head;
} else {
//存至尾端
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
死循环判断是否需要再次尝试/阻塞
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//获取前一个节点
final Node p = node.predecessor();
//如果前一个节点为头节点 尝试cas获取锁一次
if (p == head && tryAcquire(arg)) {
//获取成功 将当前节点设置为头节点
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//没有获取到锁:1.前面不只1个节点 2.前面只有一个节点 但是需要抢的锁资源未释放
if (shouldParkAfterFailedAcquire(p, node) &&
//调用 LockSupport.park
//返回是否被中断,中断后不会抛出异常,直接返回是否被中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
阻塞前,设置SIGNAL状态
设置prev前一个节点的节点为SIGNAL状态,旨在告知prev节点执行完毕后需要唤醒后续节点, 那么此时当前节点就可以准备进入阻塞状态了
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//SIGNAL -1,CONDITION -2,PROPAGATE -3, 1 CANCELLED
int ws = pred.waitStatus;
//如果前面的节点已经是 SIGNAL 就返回true ,代表进入阻塞,等待前面节点唤醒
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
//已取消
do {
//当前节点的prev指向前面未取消的节点
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//双端的 前面的也要指过来
pred.next = node;
} else {
//设置前面节点为SIGNAL 代表需要等待前面节点通知,此时返回false不进入阻塞状态,再度尝试一次才会阻塞,且再度的尝试会确保cas一定成功
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
线程被中断后 移除队列
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;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
//当前为尾部节点 重新设置尾部节点
if (node == tail && compareAndSetTail(node, pred)) {
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;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//找到node的下一个节点 或者 从尾部往前找到第一个未取消的节点,调用 LockSupport.unpark唤醒
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
唤醒后续节点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
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;
}
if (s != null)
LockSupport.unpark(s.thread);
}
释放锁逻辑
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//存在重入情况下 一次释放不完全,所以有if判断
if (tryRelease(arg)) {
Node h = head;
//head节点不为空且状态非0 表示后续有节点需要唤醒
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//次数-1 ,未重入时 -1就等于0了
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//只有当当前线程就是队列的下一个节点的时才会去抢锁
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;
}
}
可以看到公平锁和非公平锁的区别就是 严格按照FIFO先来后到的顺序,不去尝试直接抢锁
lock.newCondition()
final ConditionObject newCondition() {
//就创建了一个ConditionObject对象
return new ConditionObject();
}
//每个ConditionObject内部都维护了一个 头 尾指针, 这表明ConditionObject内部也自成一个队列
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
}
condition.signal()
一般操作是await 然后才会signal, 我们先看下signal方法,因为有可能node的状态莫名被其他线程改变了,然后一头雾水
public final void signal() {
//校验当前线程是不是持锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//上面说到 ConditionObject内部有两个节点,组成一个队列, 这里就看取出头部进行操作
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal
private void doSignal(Node first) {
do {
//将队列头部的下一个节点变为头部
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//将CONDITION状态修改为0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//前文有介绍 加入到同步队列的尾部, 同时返回原队列尾部
Node p = enq(node);
int ws = p.waitStatus;
//如果原队尾节点取消了,就将其状态改为signal,唤醒当前节点的线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
也就是说signal方法就是从ConditionObject的条件队列里的头部节点挪至同步队列。 是不是和 object.notify很像(notify是移动是cxq/EntryList的头部)
那么相应的signalAll()就是把条件队列的所有节点挨个放入同步队列的尾部。
condion.await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将线程包装为状态为CONDITION的Node 存放到ConditionObject的尾部
Node node = addConditionWaiter();
//一次将锁标志位清0,并唤醒同步队列的下一个线程
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果自己debug,多线程抢锁/signal,下面这个while可能进不去,有可能当前线程刚把自己放到条件队列的尾部,下一个线程就给他挪到同步队列了
while (!isOnSyncQueue(node)) {
//此时在条件队列 没有其他线程给他挪到同步队列 就进入阻塞了
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//此时线程未阻塞 死循环尝试竞争锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
判断是否已进入同步队列
final boolean isOnSyncQueue(Node node) {
//锁竞争不那么激烈 一般是直接返回false了
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
//有可能自己是同步队列尾部节点,从尾部向前查找,找到就证明已经在同步队列里
return findNodeFromTail(node);
}
那么await()方法就是将当前线程包装为CONDITION的Node 存放到ConditionObject的尾部并进入阻塞,等待其他线程调用signal/sinalAll方法将Node节点移至同步队列,如果进入阻塞前已被挪至同步队列,会尝试获取锁,获取不成功再进入阻塞
object.notify/notifyAll是提取到cxq/EntryList的队首,所以会优先唤醒,而这里仍是队尾
总结
ReentrantLock底层是AQS抽象队列同步器,可根据需要创建公平锁和非公平锁。
公平锁和非公平锁的区别就是非公平锁会在抢锁之初直接尝试cas操作,哪怕有其他线程正在阻塞等待。
AQS内部维护一个双向链表作为同步队列,未抢到锁的线程通通进入队列阻塞等待
除此之外,可以使用lock.newCondition()获取一个ConditionObject对象,ConditionObject内部维护一个单向的链表条件队列,使用condition.await()会将自身持有的锁资源释放,唤醒同步队列的下一个节点,同时还会将自身放入条件队列,后进入阻塞状态。调用condition.notify/nofifyAll会将条件队列的头部节点放入到同步队列进行排序获取锁资源,区别是挪动一个还是多个节点。
await()/signal()同 Object.wait() 和 Object.notify()都需要自身持有锁资源才能调用,防止引起不必要的锁竞争和造成错误。