从ReentrantLock的lock方法开始
public void lock() {
sync.lock();
}
当我们调用ReenTrantLock的lock方法时,内部调用了Sync内部类的lock方法,让我们看一看Sync类
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
}
可见Sync是一个抽象类,lock方法由具体的子类来实现,我们都知道ReentrantLock支持公平锁和非公平锁,所以ReentrantLock内部有两种lock方法的实现!我们这里从公平锁入手
FairSync公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// ...省略
}
}
在FairSync类的lock方法中,调用了acquire()方法,但是FairSync类中仅有两个方法,并没有看到acquire方法的身影,当我们在idea中按住ctrl点击此方法时,我们就进入了AbstractQueuedSynchronizer类中!
AbstractQueuedSynchronizer类初识
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
acquire方法调用了一个tryAcquire方法,此方法是抽象方法,在FairSync类中对其进行了实现,如果tryAcquire方法返回true,代表抢占锁成功,否则失败!我们先大概过一下这个方法的逻辑
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;
}
- 获取到当前线程
getState方法是在AbstractQueuedSynchronizer类中,返回此类中的state属性- 如果
state的值为0,代表此时锁没有被线程抢占 - 如果
state大于0,代表锁被线程抢占了
- 如果
- 当state为0时,代表此时锁没有被线程抢占,
hasQueuedPredecessors()方法是判断是否有线程在等待锁,由于是公平锁,如果已经有线程在等待,那么当前线程就不能获取锁,具体逻辑后面补充;如果当前没有线程等待锁,就使用compareAndSetState(0, acquires)设置state的值,如果设置成功,当前线程就抢到了锁,那么就使用setExclusiveOwnerThread(current)方法来记录那个线程抢到了锁,此方法在AbstractQueuedSynchronizer类的父类AbstractOwnableSynchronizer中,此类的方法逻辑很简单,就不多叙述了private transient Thread exclusiveOwnerThread; protected final void setExclusiveOwnerThread(Thread thread) { exclusiveOwnerThread = thread; } protected final Thread getExclusiveOwnerThread() { return exclusiveOwnerThread; } - 如果
state大于0,判断当前线程是不是锁的持有者,如果是就增加state的值,这里体现了ReentrantLock的可重入性 - 如果以上的情况都不满足,证明获取锁失败,返回
false
AbstractOwnableSynchronizer类中的Node节点
让我们回到AbstractOwnableSynchronizer的acquire方法中
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在分析方法之前,我们需要先了解AbstractOwnableSynchronizer类中的Node类
static final class Node {
static final Node EXCLUSIVE = null;
static final int SIGNAL = -1;
static final int CANCELLED = 1;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node() {
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
}
这里只列出了Node支持独占锁部分的属性,下面逐个解释
EXCLUSIVE属性代表当前节点所指向的线程是否是独占锁模式SIGNAL的值为-1,代表当前线程是在释放锁时有义务去唤醒同步队列中阻塞的线程CANCELLED的值为1,代表当前线程取消获取锁waitStatus代表Node的状态,初始值为0prev和next是Node的前后指针thread是Node代表的阻塞线程
在AbstractOwnableSynchronizer类中维护了一个双向同步队列,上方分析的Node就是队列中的节点,需要注意的是这个队列的头结点是空节点,或者说是当前占据锁的线程代表的节点。
是时候分析AbstractOwnableSynchronizer类中的精髓了,当线程占用锁失败后,会被addWaiter方法加入到同步队列中
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
addWaiter 将线程加入到同步队列中
private transient volatile Node head;
private transient volatile Node tail;
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节点,设置模式为Node.EXCLUSIVE模式,传入当前线程,需要注意的是waitStatus没有被初始化,所以waitStatus的值是0 -
tail节点就是双向同步队列的尾结点- 如果
tail节点不为null,证明队列已经被初始化了,进入if语句- 将新建节点的
prev指针指向tail,然后使用cas设置新建节点为tail节点,如果设置成功,使old tail节点的next指针指向新建节点,此时新的tail节点出现了
- 将新建节点的
- 如果
-
如果队列没有被初始化或者是cas设置新的node为tail节点失败了,进入到enq方法
-
enq方法是一个自旋操作
-
如果tail节点为
null,就初始化队列,新建一个空节点作为队列的head,将tail指向head;需要注意的是head节点是一个没有数据的节点,并且在设置的时候使用了cas,保证队列只被初始化一次 -
初始化了队列之后继续循环,跟
addWaiter方法的逻辑一样,使用cas设置新的tail节点;这里有一个需要注意的细节,当cas设置node为新的tail节点以后,当前线程有可能来不及设置old tail节点的next指针指向新tail
-
独占锁的核心acquireQueued方法
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; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
将node加入到同步队列之后,就进入了acquireQueued方法,这个方法也是一个自旋方法
node.predecessor()获取到node的前一个节点- 如果
node的前一个节点是head节点,那么就尝试去获取锁;如果说node的前一个节点是head节点,证明node是同步队列中的第一个被阻塞的节点,在node加入队列的过程中,持有锁的线程有可能已经释放锁,所以这里node才会去尝试获取锁;获取锁成功后,设置node为头结点,所以head节点也可以看成当前获取到锁的线程,只是head的thread属性为空
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
- 如果上方的条件不满足或者尝试失败,就会进入
shouldParkAfterFailedAcquire(p, node)方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
- 这里需要详细解释一下
SIGNAL的含义,SIGNAL就相当于一个闹钟,如果某一个线程的状态是SIGNAL,那么在这个线程释放锁的时候有义务去唤醒同步队列中阻塞的线程,if (ws == Node.SIGNAL)这个条件为真的话,代表node的闹钟已经设置了,在前一个节点释放锁的时候一定会来唤醒我,那我只管睡觉等待就好了。,最终shouldParkAfterFailedAcquire方法返回true,就会将当前线程阻塞。 if (ws > 0)代表CANCELED状态,证明前一个节点所代表的线程已经放弃获取锁了,那就进行清除处理,将同步队列中所有状态为CANCELED状态的节点清除else的情况就是前一个节点的状态为0,那么我们就帮前一个线程设置下状态为SIGNAL,为自己设置好闹钟,免得在前一个线程释放锁之后我还没睡醒,最后返回false去重新尝试获取锁
最后就是parkAndCheckInterrupt()方法,此方法就是将线程阻塞;在线程苏醒之后并且返回线程的阻塞标记并且重置阻塞标记
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
返回线程阻塞标记的原因是线程在阻塞的过程中有可能被调用interrupt方法,但是此时线程不能及时响应,那么我们应该给他记录下来,当线程获取退出循环后,在进行处理;
这里说一下什么时候节点的状态会进入到CANCELED状态
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
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);
}
在上方的代码中,如果循环中断,并且failed为true那么就会设置节点的状态为CANCELED,这种情况只有tryAcquire方法抛出异常才会出现!如果是ReentrantLock的话只有在重入次数太多才会导致这种现象
线程释放锁
public void unlock() {
sync.release(1);
}
线程释放锁是调用了AbstractOwnableSynchronizer类的release方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
AbstractOwnableSynchronizer类会先调用子类的tryRelease方法,在Sync类中提供了具体实现
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;
}
- 获取到
state,然后减去1 - 如果当前线程不是获取到锁的线程,就抛出异常
- 如果
state的值为0了(因为锁是可重入的),可以释放锁,设置锁的持有线程为null - 返回
free
下面的逻辑才是重点
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
h != null即head不为null,证明队列中有阻塞的线程
h.waitStatus != 0 这个条件很有意思,我们需要先思考什么时候h.waitStatus等于0,什么时候h.waitStatus != 0
- 在初始化的时候
h.waitStatus=0 - 在
shouldParkAfterFailedAcquire方法中,后继节点会设置前面节点的waitStatus为SIGNAL,此时h.waitStatus!=0
所以说如果h.waitStatus=0,证明h的后继节点没有来的急设置h的waitStatus状态,证明后继节点所代表的线程还在运行,就不用去唤醒了。
这也是为什么后继节点在shouldParkAfterFailedAcquire方法把前一个节点的waitStatus设置为SIGNAL后会继续尝试获取锁。因为前面的节点有可能正在执行release方法,并且在后继节点还没有来的急设置状态的时候执行完毕了release方法,如果后继节点直接沉睡,那么就永远不会被唤醒
如果h.waitStatus != 0,那么后继节点所代表的的线程很有可能被阻塞了,需要去唤醒
下面看一下unparkSuccessor的逻辑
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);
}
- 如果头结点的
waitStatus小于0,设置waitStatus为0 - 然后从同步节点的最后一个节点开始寻找最靠近头结点并且
waitStatus小于等于0的节点- 为什么从最后一个节点开始而不是从第一个节点开始呢?其实上面已经埋下了伏笔,在
addWaiter的时候,新的tail节点有可能还没有被old tail节点的next指针连接,所以如果从前往后遍历的话就会少遍历一个节点
- 为什么从最后一个节点开始而不是从第一个节点开始呢?其实上面已经埋下了伏笔,在
- 在获取到节点后直接去唤醒
hasQueuedPredecessors方法
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 如果
h != t && (s = h.next) == null时,证明addWaiter方法被调用,但是cas设置新的tail成功了,还没有成功设置tail.prev=h,不管怎么说,此时队列中是有节点的,返回true - 如果
h != t && s.thread != Thread.currentThread()时,证明第一个队列中的第一个有效节点不是当前线程,那么返回true;也就是说,如果当前获取锁的线程是队列中的第一个有效节点(头结点无效的),那么这个线程有获取锁的资格
推荐一篇写的非常好的文章:segmentfault.com/a/119000001…