ReentrantLock
Reentrantlock 的本质就是通过操作 AbstractQueuedSynchronizer 中的state字段,实现加锁解锁和可重入锁的功能, state 为 1 和 0 表示加锁和解锁成功,state > 1 的时候用来记录重入锁的次数;
AQS本身是一个双端链表,每个阻塞的线程对应链表中的一个node节点;
有两个变量需要注意下: state 维护的是整个队列的状态,或者叫锁的状态, waitStatus 是node中的属性,维护的是当前节点或者对应线程在解锁的时候要触发的动作;
waitStatus对应的值以及对应要处理的动作:
状态字段,取值如下:
SIGNAL: 该节点的后继节点当前或即将被阻塞(通过 LockSuport.park()方法),因此当前节点必须在释放或取消时唤醒其后继节点
CANCELLED: 该节点由于超时或中断而被取消。节点永远不会离开此状态。特别是,具有取消节点的线程不会再阻塞。
CONDITION: 该节点当前在条件队列中。它不会被用作同步队列节点,直到被转移,此时状态将被设置为 0。这里使用这个值与其他字段的用途无关,但简化了机制。
PROPAGATE: 应该将 releaseShared 传播到其他节点。这在 doReleaseShared 中为头节点设置,以确保传播继续进行,即使其他操作已介入。
0: 无上述情况
这些值按数字排列以简化使用。非负值表示节点不需要信号。因此,大多数代码只需检查符号,而不必检查具体值。
字段初始化为 0(对于正常的同步节点)或 CONDITION(对于条件节点)。它通过 CAS(或在可能的情况下,不带条件的 volatile 写入)进行修改。
源码阅读:
非公平锁:
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
lock():
ReentrantLock reentrantLock = new ReentrantLock();
reentrantLock.lock();
lock():
public void lock() {
sync.lock();
// abstract void lock(); 抽象实现(对应公平锁和非公平锁)
}
NonfairSync#sync.lock():
final void lock() {
// 会先执行一次cas操作,如果成功就直接将当前线程配置为锁的拥有者
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
// cas失败就通过aqs等待获取锁
else
acquire(1);
}
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
AbstractQueuedSynchronizer#acquire(1):
这个方法先执行 tryAcquire 尝试获取锁,如果获取失败就执行 addWaiter 将当前node加入到AQS队尾,然后在执行 acquireQueued方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//非重入锁,在执行一次cas操作,也就是说获取锁失败以后,并不是直接就加入队列中了,而是又重新获取了一下
//因为前一个线程可能在当前线程两次检查中间就释放了锁
//这样做的目的是因为阻塞和唤醒都需要系统调用,很耗时,尽可能减少这种操作
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//重入锁的逻辑 state++
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;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter:
这个是加入队列的方法,逻辑包含两部分,
一种是队列还没有初始化,这时候需要执行enq(node);
另一种是已经初始化,执行compareAndSetTail操作,直接将node添加到队尾;
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;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#enq:
初始化队列,并将node添加到队尾,这里除了添加了当前node节点,还添加了一个空的head节点,
普通的节点会调用这个构造函数初始化 Node node = new Node(Thread.currentThread(), mode);
head节点直接调用了这个构造函数 new Node();
这个空head节点其实就充当了当前已经获取到锁的那个线程,以为这个队列的初始是在获取锁失败的情况下才会创建的,说明此时锁资源已经被一个线程占用了,可以看下这里 NonfairSync#sync.lock()
这个head节点的waitStatus值为默认值0;
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;
}
}
}
}
此时队列的状态是这样子的:
java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireQueued:
addWaiter中已经初始化队列并且添加了一个node(ThreadA)节点了
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 获取当前节点的前驱节点
final Node p = node.predecessor();
//判断当前节点的前驱节点是否为head节点,
//是的话(代表是队列中下一个需要被唤醒的就是当前节点)重新尝试获取锁信息
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 走到这里要不就是前驱节点不是head节点
//(当前线程还不是下一个要被唤醒的线程,就和排队吃饭一样,下一个打饭的人还不是你,你继续等吧)
// 要不就是我已经是下一个要被唤醒的线程了,但是因为现在是非公平的情况,会有被插队的可能
// java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire 被这个方法插队
// 我们都是老实人,多一事不是少一事,我在继续等等吧
// 还有就是前一个线程还没有释放锁,你急了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire:
接着上面的说,进入这个方法有三种情况: 前驱节点还不是head节点;或者被插队;或者上一个线程还没有释放锁
现在队列中 有一个 head 节点和一个 threadA节点,所以 threadA的前驱节点就是head,比如现在是第三种情况,上一个线程还没有释放锁,这时候 tryAcquire 会失败,这时候会执行下面这个方法中 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);操作,将前驱节点的 waitStatus 设置为 SIGNAL,也就代表着前驱节点释放锁以后,会唤醒当前线程;
如果是被插队,证明上一个执行的线程已经释放锁了,执行了unlock方法,他会将头节点waitStatus < 0 的状态置为 0
这时候当前线程也会执行下面这个方法中 compareAndSetWaitStatus(pred, ws, Node.SIGNAL);操作;(这时候你可能会有疑问,head节点的线程都执行完成了,谁还会唤醒当前这个阻塞的线程啊,这里就要说一下 unlock 方法,他在唤醒的时候都是会从head节点开始,所以不管当前执行的线程是哪一个,释放的锁的时候都会从 head.next 节点开始唤醒 ),所以当插队线程执行完成以后,同样会唤醒我们的当前阻塞线程;
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
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,这样前驱节点就会知道要唤醒他的后继节点
// 类似向前驱节点注册了一个感兴趣的事件
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt:
这个方法就很简单了,通过 LockSupport.park阻塞当前线程;并返回中断标识;
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#selfInterrupt:
这个方法就是处理中断的,这也是为啥 使用 reentrantLock.lock() 的时候,不需要处理中断异常,因为这里只是改变了中断标识,如果需要对中断进行处理,就需要我们自己在代码中 reentrantLock.lock() ;后面判断中断标识,处理逻辑;
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
公平锁的区别:
java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire:
主要就是这个方法的区别,在cas操作之前执行了 hasQueuedPredecessors 函数,这个函数的作用就是判断队列中是否有元素,有的话你就不能抢锁,也要去排队;就是少了上面插队的情况,世界更加和谐一点;
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;
}
下面说一下unlock的逻辑:
java.util.concurrent.locks.AbstractQueuedSynchronizer#release:
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 这里就是上面说到的,任何线程释放锁的时候,都会从head节点开始
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
java.util.concurrent.locks.ReentrantLock.Sync#tryRelease:
这个方法的作用就是 判读 state 状态是否为 0,如果为 0 说明 解锁次数和加锁次数平衡,可以解锁;
如果不为 0 ,说明是重入锁,只做递减操作,不会唤醒线程;
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;
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor:
如果能够执行到这里,说明 state 已经被重置为 0 ;
这里就是唤醒线程的逻辑了;
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;
// 将节点的状态重置为 0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 因为节点有 CANCELLED 的状态,如果被关闭了,就不需要唤醒当前线程了,跳过
// 这里采用的是从 tail向前遍历,目的都是一样的
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.park 对应
LockSupport.unpark(s.thread);
}
condition.signal() 方法:
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
java.util.concurrent.locks.ReentrantLock.Sync#isHeldExclusively:
判断一下当前这个线程是不是持有锁的线程,不是的持有的,你释放个锤子啊;
protected final boolean isHeldExclusively() {
// While we must in general read state before owner,
// we don't need to do so to check if current thread is owner
return getExclusiveOwnerThread() == Thread.currentThread();
}
java.util.concurrent.locks.AbstractQueuedSynchronizer.ConditionObject#doSignal:
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
java.util.concurrent.locks.AbstractQueuedSynchronizer#transferForSignal:
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
// 通过状态维护了多个队列
// condition中的状态都是 CONDITION
// 如果状态变成了 0 ,也就是添加到 aqs队列中的初始值
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
// 这里就是将节点从 condition 队列中转移到 aqs 队列的逻辑,重新入队
Node p = enq(node);
int ws = p.waitStatus;
// 这个 p 是当前节点的前驱节点,不是node节点本身
// 如果前驱节点被关闭 或者 更新 SIGNAL 失败,则直接唤醒当前线程
// 否则只是修改前驱节点的状态为 SIGNAL,然后当前线程继续阻塞,唤醒的逻辑和前面aqs就一样了
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}