文章目录
- 类结构
- 类结构图
- 核心类
- 源码探索
- lock()
- AbstractQueuedSynchronizer.acquire()
- AbstractQueuedSynchronizer.addWaiter()
- ReentrantLock.FairSync.tryAcquire()
- AbstractQueuedSynchronizer.acquireQueued()
- AbstractQueuedSynchronizer.selfInterrupt()
- unlock()
- AbstractQueuedSynchronizer.release()
- ReentrantLock.Sync.tryRelease()
- AbstractQueuedSynchronizer.unparkSuccessor()
- Node链表流转情况(流转图)
类结构
类结构图
核心类
AbstractQueuedSynchronizer.acquire()(AQS):提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。
ReentrantLock,实现了Lock的统一锁接口。锁功能是基于AbstractQueuedSynchronizer实现的。声明的内部类Sync就是继承自AbstractQueuedSynchronizer,是一种模板设计模式
NonFairSync和FairSync是Sync的子类,NonFairSync是非公平锁的实现,FairSync为公平锁实现。
Node,是AbstractQueuedSynchronizer的内部类,是AQS队列的实现,每个节点都有前后节点。并且标识了当前的状态,和节点存储的线程。
源码探索
以FairSync公平锁的实现看下lock()方法和unlock()方法的逻辑
lock()
AbstractQueuedSynchronizer.acquire()
FairSync.lock()方法中会调用acquire(1)方法申请锁,这个方法会调用父类AbstractQueuedSynchronizer.acquire()方法。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个方法中**tryAcquire()尝试获取锁,如果失败调用acquireQueued()方法加入排队,并且自旋判断进入阻塞;在加入排队之前调用addWaiter()**方法来为当前线程创建节点,并加入链表中。
AbstractQueuedSynchronizer.addWaiter()
在排队的双向链表中增加节点
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;
// 如果tail也就是最后一个节点不为空
if (pred != null) {
// 将原来的tail节点设置为新节点的上一个节点
node.prev = pred;
// 将新节点CAS成tail节点
if (compareAndSetTail(pred, node)) {
// 原tail节点的下个节点为新节点
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
// 自旋
for (;;) {
Node t = tail;
// 如果tail节点为空,则进行初始化链表
if (t == null) { // Must initialize
// 新建一个空节点,并且CAS掉Head节点
if (compareAndSetHead(new Node()))
// head节点赋值给tail节点
tail = head;
}
// 第二次的情况t就不可能为空了。
else {
// 当前线程新建的节点的上一节点为head节点
node.prev = t;
// 设置当前线程新建的节点CAS为tail节点
if (compareAndSetTail(t, node)) {
// 头节点的下一个节点为新建节点
t.next = node;
return t;
}
}
}
}
addWaiter的逻辑:
- 新建一个当前线程的Node节点。
- 如果原tail不为空,则将原tail节点的下一个节点设置成新的Node节点,新Node节点设置成tail节点,并且上级节点为原tail节点,程序然后返回当前节点
- 如果原tail为空,则调用**enq()**方法,该方法进行自旋。如果tail节点为空,则进行链表初始化,创建一个空节点为Head节点。将新建的节点设置成tail节点,并且和Head节点简历双向链表
ReentrantLock.FairSync.tryAcquire()
尝试获得锁
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取当前节点的状态,默认一开始为初始状态,为0;并且state为volatile的
int c = getState();
if (c == 0) {
// hasQueuedPredecessors判断前面是否有排队的;
// compareAndSetState将同步器状态CAS成1,1为取消状态(也可理解为拿到锁状态),因为拿到锁了,队列节点就取消等待了。
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
// 设置拿到排他锁的线程为当前线程
setExclusiveOwnerThread(current);
// 返回获得锁
return true;
}
}
// 实现重入锁:Node.state不为0,判断拥有锁的线程是否当前线程,如果是那说明是锁重入
else if (current == getExclusiveOwnerThread()) {
// 每进入一次则状态 +1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
// 返回获得锁
return true;
}
// 都不是则没有拿到锁,返回false
return false;
}
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
// 是否还存在排队的判断逻辑:
// head和tail不是同一个,并且head的下一个不为空。并且head的下一个的线程不为当前线程。
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
tryAcquire的逻辑:
- 先判断当前节点为初始状态,则判断前面是否还有排队的节点,如果没有则CAS更新当前节点的状态为1(拿到锁状态),并且设置当获取排他锁的线程为当前线程。
- 如果状态不为初始状态,则进行重入判断,判断锁线程是否为当前线程。并且在当前节点的状态+1 。
- 即不是重入锁也没拿到锁,则返回false
AbstractQueuedSynchronizer.acquireQueued()
将新建的节点加入到队列进行排队,并且将当前节点的线程进入阻塞。
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)) {
// 加锁成功了,将当前节点设置为head节点,将prev置空
setHead(node);
// 将原head节点的next置空,脱链帮助GC回收
p.next = null; // help GC
failed = false;
// 返回阻塞为false
return interrupted;
}
// 如果前一个节点不是head节点
// 进行对前一个节点的状态的判断,来决定当前节点是否应该阻塞,
if (shouldParkAfterFailedAcquire(p, node) &&
// 阻塞并检查中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// failed正常情况都为false,除非自旋阻塞最后都没有进到获取锁里面
if (failed)
// 取消进行中的锁申请
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// -1 说明前一个节点正常阻塞
if (ws == Node.SIGNAL)
// 如果前一个节点阻塞,当前节点就正常阻塞
return true;
// 如果前一个节点大于0,则说明已经放弃了。
if (ws > 0) {
do {
// 链表中剔除前一个放弃状态的节点
node.prev = pred = pred.prev;
// 如果前一个状态还是大于0,继续往前找。
} while (pred.waitStatus > 0);
// 将找到非放弃的节点,设置下一个节点为当前节点
pred.next = node;
} else {
// 如果状态不是阻塞也不是放弃,相当于0或者<-1的状态,将前一个节点状态CAS成-1阻塞。等待唤醒
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
// 进行线程阻塞
LockSupport.park(this);
// 此处检查阻塞是否被中断,该方法不仅检查中断还会重置中断标志。
return Thread.interrupted();
}
acquireQueued()方法逻辑:
- 阻塞排队,循环判断尝试获取锁。
- 判断当前节点的前一个节点是否是Head节点,如果是说明排队到头了,可以进行锁获取。如果获取到锁将当前节点设置成Head节点(Head节点中thread为null),原Head节点剔除出链表。
- 如果前一个节点不是Head节点或者tryAcquire()没有获得锁,则进行阻塞判断并调用LockSupport.park()阻塞。
- 阻塞判断方法中,核心逻辑是将链表中当前节点之前的节点放弃的则剔除,排队的置为-1。这个方法返回false,调用的代码会持续循环
- 阻塞释放之后,继续循环判断头节点和尝试获取锁,拿到锁之后则返回。
AbstractQueuedSynchronizer.selfInterrupt()
设置当前线程中断标识
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
在parkAndCheckInterrupt()方法中等待阻塞被释放,则继续循环尝试获取锁。阻塞被释放存在正常释放(unpack())和阻塞中断释放,调用Thread.interrupted()方法判断是否阻塞中断释放,会将线程的中断标志重置调;所以在再次获取锁之后,还要进行线程中断标识的设置
unlock()
AbstractQueuedSynchronizer.release()
释放锁unlock()方法是调用了AbstractQueuedSynchronizer.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;
}
release()方法主要干了两件事情
1、尝试释放锁
2、释放成功之后,对Head节点检查,唤醒排队的后继者
ReentrantLock.Sync.tryRelease()
释放锁
protected final boolean tryRelease(int releases) {
// 释放重入锁,每次释放状态-1
int c = getState() - releases;
// 如果当前线程,不等于锁所有者的线程,则抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果c为0,则说明重入锁也都释放完成
if (c == 0) {
// 完成释放则返回成功
free = true;
// 锁拥有者的线程置空
setExclusiveOwnerThread(null);
}
// 更新当前节点状态
setState(c);
return free;
}
tryRelease()方法逻辑:
判断锁持有者是否当前线程。判断当前节点状态-1 是否为0 ;将锁拥有者线程置空,释放锁。如果最后状态不为0,返回释放锁失败
AbstractQueuedSynchronizer.unparkSuccessor()
唤醒下一个后继者
private void unparkSuccessor(Node node) {
// 当前节点为负数,则将节点状态置为0
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 检查下一个节点
Node s = node.next;
// 下一个节点为空或者状态大于0
if (s == null || s.waitStatus > 0) {
s = null;
// 循环从尾部往前找,直到找到离当前node最近的一个符合等待状态的节点
for (Node t = tail; t != null && t != node; t = t.prev)
// 找到一个节点状态小于0的,说明在等待
if (t.waitStatus <= 0)
// 先将等待的节点赋给s
s = t;
}
// 存在下一个节点,则将下一个节点唤醒
if (s != null)
// 将下一个排队者唤醒
LockSupport.unpark(s.thread);
}
unparkSuccessor逻辑:
- 获取当前节点的下一个节点,是否满足条件,满足则唤醒。
- 不满足则循环从尾部往前找,找到离当前Node最近的一个满足条件的节点。
- 将符合条件的排队节点唤醒
Node链表流转情况(流转图)