AQS之独占锁ReentrantLock源码分析
1:AQS原理分析
java.util.concurrent包中的大多数同步器实现都是围绕着共同的基础行为,比如等待队列、条件队列、独占获取、共享获取等,而这些行为的抽象就是基于AbstractQueuedSynchronizer(简称AQS)实现的,AQS是一个抽象同步框架,可以用来实现一个依赖状态的同步器。
1.1:AQS具备的特性
- 阻塞等待队列
- 共享/独占
- 公平/非公平
- 可重入
- 允许中断
1.2:AQS的state状态
- 值为0,初始化状态,表示当前节点在sync队列中,等待着获取锁。
- CANCELLED,值为1,表示当前的线程被取消
- SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark;
- CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中;
- PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行;
1.3:AQS两种共享资源:独占锁,共享锁
独占锁就是多个线程,只有一个线程能获取这把锁:ReentrantLock(独占锁)
共享锁就是可以设置多个线程共同使用这把锁: Semaphore(信号量)和CountDownLatch(计数器)
2:ReentrantLock加锁分析
ReentrantLock是一种基于AQS框架的应用实现,synchronized是JDK中的一种线程并发访问的同步手段,它的功能类似于synchronized是一种互斥锁,可以保证线程安全。下面为加锁流程图:
相对于 synchronized,ReentrantLock具备如下特点:
- 可中断
- 可以设置超时时间
- 可以设置为公平锁
- 支持多个条件变量
- 与 synchronized 一样,都支持可重入
2.1 lock 线程获取锁:
final void lock() {
//进来的线程首先cas尝试获取锁
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//失败,获取锁
acquire(1);
}
2.2 acquire -》获取锁
public final void acquire(int arg) {
//判断1:尝试获取锁,判断2:将线程放入链表并且中断或者获取锁
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//标记中断
selfInterrupt();
}
2.3 tryAcquire–》尝试获取锁
1:先通过cas去修改线程状态,查看是否修改成功(尝试获取锁)
2:如果正在运行的线程是需要获取锁的线程,状态值+1记录重入次数(重入锁)
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前线程状态
int c = getState();
if (c == 0) {
//cas-》可运行状态尝试获取锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前线程是否是正在运行的线程
else if (current == getExclusiveOwnerThread()) {
//重入锁+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
//进行重入锁赋值
setState(nextc);
return true;
}
return false;
}
2.4 acquireQueued-》将线程放入列表
1:获取前置节点,判断是否为头节点,如果不是直接阻塞,等待获取解锁,如果是就进行一次cas获取锁操作,
final boolean acquireQueued(final Node node, int arg) {
//判断如果是否失败
boolean failed = true;
try {
//中断标记
boolean interrupted = false;
for (;;) {
//获取node的前置节点
final Node p = node.predecessor();
//判断是否为头节点,进行cas获取锁
if (p == head && tryAcquire(arg)) {
//设置当前节点为头节点
setHead(node);
//设置下个节点为空,gc会判断是否是无用数据进行回收
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果未获取到锁,或者不是头节点进行状态值更新
if (shouldParkAfterFailedAcquire(p, node) &&
//进行线程中断
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
2.4.1 acquireQueued中的addWaiter-》添加节点
1:添加节点,判断尾节点是否存在,不存在那么就通过enq进行初始化操作,存在将当前节点放入链表中。
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;
//cas链表的尾节点添加当前线程,形成双向绑定
if (compareAndSetTail(pred, node)) {
//链表的尾节点添加当前线程,形成双向绑定
pred.next = node;
return node;
}
}
//如果尾节点为空,进行初始化
enq(node);
return node;
}
2.4.2 enq-》初始化链表结构
1:100%入队,首先将节点赋值给尾节点,然后二次循环将尾节点赋值给头节点形成双向绑定
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;
}
}
}
}
2.4.3 shouldParkAfterFailedAcquire-》设置节点状态
1:将前驱节点设置为-1,方便获取唤醒
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前一个节点状态
int ws = pred.waitStatus;
//如果为== -1 直接返回
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前面的节点状态进行修改
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.
*/
//cas 修改节点状态
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
3 ReentrantLock解锁分析
3.1 release(int arg)-》释放锁
1:尝试解锁,进入tryRelease(arg),如果解锁成功则进行unparkSuccessor(h)操作
public final boolean release(int arg) {
//尝试解锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//解锁
unparkSuccessor(h);
return true;
}
return false;
}
3.2 tryRelease-》尝试释放锁
1:判断当前线程是否是运行线程
2:获取当前锁状态-解锁数,判断是否是重入锁,如果不算重入锁返回ture,如果是重入锁,则进行锁-1
protected final boolean tryRelease(int releases) {
//获取当前锁状态-解锁数 可重入锁的原因
int c = getState() - releases;
//如果当前线程不是运行线程抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
//如果当前重入锁数量为0进行解锁
free = true;
setExclusiveOwnerThread(null);
}
//将计算完毕后的锁数量赋值
setState(c);
return free;
}
3.3 unparkSuccessor-》释放锁
1:进行解锁操作,它会从尾节点开始寻找最顶层的节点是否为-1,如果为-1那么就是阻塞线程,获取阻塞线程节点进行解锁操作
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,进行cas修改当前节点状态
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.
*/
//获取当前节点的下一个节点
Node s = node.next;
//判断下一个节点状态,如果下一个节点为null或者大于0
if (s == null || s.waitStatus > 0) {
s = null;
//循环获取尾节点,判断尾节点不为空并且不等于node当前节点,t节点等于t的上一个节点(从尾节点开始寻找可执行节点)
for (Node t = tail; t != null && t != node; t = t.prev)
//t的上一个节点为-1,那么s=t
if (t.waitStatus <= 0)
s = t;
}
//s不等于null,唤醒s节点
if (s != null)
LockSupport.unpark(s.thread);
}
4 是否为公平锁问题分析
1:是否是公平锁,无非就是是否存在排队的情况,那么在看源码的时候你们会发现公平锁和非公平锁其实就加了一个判断头节点是否存在的问题,非公平锁是不存在头节点判断那么在线程进入时就可以开始竞争,公平锁在线程进入时回去判断是否存在头节点问题。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 1. 和非公平锁相比,这里多了一个判断:头节点是否有线程在等待
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;
}
}