类图
非公平锁实现原理
ReentrantLock默认使用的是非公平锁(NonfairSync)
构造器
先从构造器开始,默认为非公平锁实现
public ReentrantLock() {
sync = new NonfairSync();
}
加锁流程
// ReentrantLock
public void lock() {
sync.lock();
}
ReentrantLock的lock方法直接调用的Sync的lock方法
加锁成功
//NonfairSync
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
可以看到利用CAS进行来争抢,如果获取成功则设置独占标记。如果失败,就执行acquire方法。
//AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//NonfairSync
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//NonfairSync
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;
}
acquire方法会首先会执行tryAcquire方法,最终会执行nonfairTryAcquire方法。nonfairTryAcquire方法中仍然会先获取state的值,如果为0,说明锁被释放了,可以继续争抢。如果成功,则设置标记后返回。state不为0时,说明锁仍被占有,但需要继续判断锁的持有者,如果是该线程自己,则可以继续获取锁。这也是ReentrantLock被称为可重入锁的原因。
加锁失败
利用CAS尝试将state从0改为1,如果失败,则执行acquire方法。先回顾一下acquire方法。
//AbstractQueuedSynchronizer
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先会执行tryAcquire,再次失败后就执行acquireQueued方法。acquireQueued第一个实参是通过addWaiter方法返回的。
addWaiter
//AbstractQueuedSynchronizer
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;
}
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;
}
}
}
}
传进来的默认的值是Node.EXCLUSIVE,使用的是排它模式。然后创建一个Node关联当前线程,并设置mode。首先会判断tail是否为null,如果是则表明该线程是第一个来排队的,使用懒加载初始化head,值为null,用尾插法并在CAS的帮助下将node放在head的后面。接下来就可以看acquireQueued方法了。
acquireQueued
//AbstractQueuedSynchronizer
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;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
方法中使用了死循环。首先判断node(关联了当前线程)的前驱节点是否是head,然后再次尝试获取锁,获取成功后就出队。否则执行shouldParkAfterFailedAcquire方法。
shouldParkAfterFailedAcquire
首先我们要知道为了编码的方便Node与Thread是相互关联的,可以把Node当作当前线程。然后在看方法之前,我们需要先了解另一个知识点:Node的waitStatus值。
static final class Node {
/** 当前线程被取消 */
static final int CANCELLED = 1;
/** 后继线程需要被当前线程唤醒 */
static final int SIGNAL = -1;
/** 当前线程处于等待状态,等待条件的发生 */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;
/**
* status的取值:
* SIGNAL: 表示此节点的后继节点被(或即将被)阻塞(通过park方法),
* 因此当前节点在释放或取消时必须唤醒其后继节点。
* 为了避免竞争,获取方法必须首先指示它们需要一个信号,然后重试原子获取,
* 如果失败则阻塞。
* CANCELLED: 表示由于超时或中断,此节点被取消。
* 处于该状态的节点不会再变为其它状态。
* 特别地,具有被取消节点的线程不会再次被阻塞。
* CONDITION: 此节点当前位于条件队列中。
* 直到转移时,它将不会被用作同步队列节点,
* 转移时状态将被设置为0。
* (此处对该值的使用与字段的其他用途无关,但简化了机制。)
* PROPAGATE: 应将releaseShared操作传播到其他节点。
* 这在doReleaseShared中设置(仅适用于头节点),以确保传播继续进行,
* 即使其他操作已经介入。
* 0: 默认状态
*
* 这些值按照数字顺序排列,以简化使用。
* 非负值表示节点不会发出信号。
* 因此,大多数代码不需要检查特定的值,只需检查符号即可。
*
* 该字段对于普通的同步节点初始化为0,
* 并且对于条件节点初始化为CONDITION,
* 它使用CAS进行修改(或在可能的情况下,进行无条件的volatile写操作)。
*/
volatile int waitStatus;
}
了解waitStatus的取值,再来看shouldParkAfterFailedAcquire方法就会轻松很多。
//AbstractQueuedSynchronizer
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 表明前驱节点已经设置为SIGNAL,前驱节点会负责唤醒当前节点的
return true;
if (ws > 0) {
// 如果前驱节点的状态为CANCELLED,则向前寻找非CANCELLED状态的节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus只会是0或PROPAGATE状态(为啥不会是condition呢?)
* 表明我们需要一个信号量,但不需要阻塞(通过park方法)
* 调用者需要重试去确保该节点阻塞前不会被获取到。
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
只有前驱节点的waitStatus为SIGNAL,才继续执行下一步,否则进入下一轮循环。下一步就是执行parkAndCheckInterrupt方法
parkAndCheckInterrupt
//AbstractQueuedSynchronizer
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
通过LockSupport阻塞当前线程,返回判断当前线程是否被打断过。到此,循环内部的逻辑就执行完成了。如果在执行过程中出现异常会执行一个finally代码块中的内容。
cancelAcquire
//AbstractQueuedSynchronizer
private void cancelAcquire(Node node) {
// 节点不存在就直接返回
if (node == null)
return;
node.thread = null;
// 跳过CANCELLED状态的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext是当前节点的非CANCELLED状态的前驱节点的下一个节点的引用
Node predNext = pred.next;
//将当前节点状态置为CANCELLED,这样就不会影响到其它线程。
node.waitStatus = Node.CANCELLED;
// 如果当前节点在尾部,将tail指向前驱节点,并将前驱节点的后继节点置为null
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 当前节点如果status是SIGNAL,则它负责唤醒下一个节点,但是由于发生了异常,
// 这项任务就需要交给非CANCELLED前驱节点
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
//predNext是已经找好的引用,如果当前节点不是head的下一个节点,
//让predNext指向当前节点的下一个节点即可。
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 {
//如果当前节点正好是head的下一个,自己状态已经变成CANCELLED了,
//直接唤醒后继节点即可。
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
//AbstractQueuedSynchronizer
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 找到为非空且状态非CANCELLED的后继节点,唤醒该节点。
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);
}
该节点在尝试获取锁的过程中如果出现了问题,就会执行cancelAcquire方法。队列中的每一个节点有一个任务就是负责唤醒后继节点。如果当前节点出现了问题,就需要找到前驱节点,让它替自己唤醒后继节点。如果不巧当前节点就是第一个节点(head后面),且后继节点有需要被唤醒的,就在自己CANCELLED之前“帮一下”。如此以来,acquireQueued也算分析完成了,到这里可能忘记从哪里出发了,我们回顾一下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
进行if语句内部,就开始执行selfInterrupt方法了。
selfInterrupt
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
打断当前线程。注意:打断当前线程,当前线程并不会立刻停止运行,只是设置一个被打断的标志,线程可以通过该标志判断自己是否被打断过,如果被打断后,则可以执行被打断的逻辑。
释放流程
public void unlock() {
sync.release(1);
}
ReetrantLock释放锁的逻辑就是直接调用unlock方法
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) {
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;
}
可以看到释放锁的逻辑是很简单的。首先会判断当前线程和锁的拥有者是否是同一个,成功后唤醒下一个。
总结
尝试加锁,加锁成功就返回,失败就入队。执行完成后要唤醒队首节点。
可打断原理
不可打断模式
我们首先来看一下不可打断模式,这也是ReentrantLock的默认模式。核心在于我们分析的selfInterrupt。
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
该方法表面上看是要打断当前线程,但其实不然,在Java中打断当前线程,当前线程并不会立刻停止运行,只是设置一个被打断的标志,线程可以通过该标志判断自己是否被打断过,如果被打断后,则可以执行被打断的逻辑。而如果想打断当前线程,可以使用lockInterruptibly方法。
//ReentrantLock
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//AbstractQueuedSynchronizer
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//AbstractQueuedSynchronizer
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以看出这三个方法与前面方法执行逻辑类似,有区别的地方在于会检查线程的打断标记来决定是否抛异常。如果线程被打断过,就抛出异常,打断线程。这里再次证明了,在Java中打断线程,线程会通过打断标记来决定是否执行被打断的逻辑。
公平锁实现原理
非公平锁的实现
//NonfairSync
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//line 5
if (compareAndSetState(0, acquires)) {//line 6
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;
}
line 5、line 6
中可以看到:在没有线程抢到锁的情况下,所有线程都还会通过CAS竞争锁,这里就表明这是非公平锁。
公平锁的实现
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {//line 4
if (!hasQueuedPredecessors() &&//line 5
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;
}
从line 4、line 5
可以看出,非公平锁的时间逻辑在于会判断队列中是否由其它线程,如果由其它线程则加锁失败,这样就可以按照FIFO的方式来获取锁。