JUC并发包下的AQS锁源码解析
AQS的定义
谈到并发就不得不说AQS(AbstractQueuedSynchronizer)所谓的AQS翻译过来就是抽象队列同步器,是用来构建锁或者其他同步组件的基础框架,它使用了一个int成员变量来表示同步状态,通过CLH对列来完成资源获取线程的排队工作。说白了就是 采用了volatile+CAS来保证加锁解锁,用队列来完成线程的排队,用park/unpark来实现线程的等待与唤醒
同步器的实现
同步器主要是通过继承的方式来实现,AQS采用模板方式,让子类来继承同步器并实现它的抽象方法来管理同步状态。
主要是
**getState()**获取当前状态,用来判断是否有线程加锁了。
setState改变state状态,1为加锁,0是代表该锁没有被线程占有。
**compareAndSetState(int expect,int update)**进行CAS操作保证原子性
ReentrantLock实现AQS
我们熟知的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基于AQS来实现的。
现在我们就来聊聊ReentrantLock底层是如何实现AQS的
ReentrantLock里分公平锁跟非公平说,我们先来说公平锁
//java\util\concurrent\locks\ReentrantLock.java
public void lock() {
sync.lock();
}
abstract void lock();
//选择公平锁的实现lock()方法
final void lock() {
acquire(1);
}
//java\util\concurrent\locks\AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
//tryAcquire是尝试去获取锁,如果成功了则放回true,失败了则返回false
//当获取锁失败则表示该锁已经被其他线程占有,该线程就要进入等待队列等待
//addWaiter添加等待node节点,
//添加好node节点放入acquireQueued()队列中,然后让该线程park
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
解析AQS怎么尝试获取锁的(公平锁)
java\util\concurrent\locks\ReentrantLock.java
/**
* Fair version of tryAcquire. Don't grant access unless
* tryAcquire的公平版。不要授予访问权限,除非
* recursive call or no waiters or is first.
* 递归调用或者没有等待者或者是第一个。
*/
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取同步锁的状态
int c = getState();
//判断同步锁的状态
if (c == 0) {
//如果同步锁的状态为0表示当前同步锁没有被任何线程所拥有
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
//因为是公平锁就算该同步锁的状态为0也不可以直接抢占锁。
//还要判断等待队列里是否有正在排队的线程,如果有,当前线程就不可以进入抢锁过程,要去队列里排队等待。假如队列里没有排队的线程,那就可以给线程上锁
setExclusiveOwnerThread(current);
return true;
}
}
//判断当前是否为占有锁的线程,如果是则重入
//lock跟sync一样都是可重入锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
接着谈谈非公平锁是怎么实现的
解析AQS怎么尝试获取锁的(非公平锁)
/**
* Performs lock. Try immediate barge, backing up to normal
* 执行锁定。尝试立即插入,恢复正常
* acquire on failure.
* 失败时获取。
*/
final void lock() {
//非公平锁一上来就尝试去获取锁,如果获取到了就占有该锁,不管等待队列里是否有在排队的线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
//如果抢锁失败则进入acquire方法,就跟公平锁差不多了
acquire(1);
}
公平锁与非公平锁的区别:
非公平锁:一上来就不管三七二十一,先抢锁,不管是否有在排队的线程
公平锁:老的线程排队使用锁,新线程仍然排队使用锁
获取锁失败后进入等待队列
java\util\concurrent\locks\AbstractQueuedSynchronizer.java
/**
* Creates and enqueues node for current thread and given mode.
* 为当前线程和给定模式创建节点并将其排队。
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
//添加一个等待队列,将lock维护的node节点传入,Node是一个双向链表
private Node addWaiter(Node mode) {
//将当前线程存入该node中
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//将队列中的尾节点赋值给pred
Node pred = tail;
//如果尾节点为空的情况,说明当前队列中没有等待的线程
if (pred != null) {
//将当前节点插入队列的尾节点
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果为空
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* 将节点插入队列,必要时进行初始化
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
//死循环,直到节点添加成功
for (;;) {
Node t = tail;
//第一遍循环时tail指针为空,进入if逻辑,使用CAS操作设置head指针,将head指向一个新创建的Node节点
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//保证头节点为空,将等待线程设置为尾节点
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
将添加好的节点存入队列中
/**
* Acquires in exclusive uninterruptible mode for thread already in
* 以独占不间断模式获取已在的线程
* queue. Used by condition wait methods as well as acquire.
* 排队。由条件等待方法和获取方法使用。
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//死循环,直到将node插入队列中并且让其park,或者获取到锁退出循环
for (;;) {
final Node p = node.predecessor();
//判断node节点是否为头节点,如果是,则表示该节点是队列中第一个排队的,就可以尝试去获取锁
if (p == head && tryAcquire(arg)) {
//如果获取锁成功了,将node节点设置为head
setHead(node);
//将该node清空,方便GC回收
p.next = null; // help GC
failed = false;
return interrupted;
}
//如果不是头节点,且获取锁失败了
//就会通过shouldParkAfterFailedAcquire方法 将head节点的waitStatus变为了SIGNAL=-1,
//则将该线程插入等待队列中,并且把它park阻塞住
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
以上就是公平锁跟非公平锁的加锁过程
接下来谈谈解锁过程
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//这里会尝试去释放锁,释放成功,
//如果state状态为零则释放锁,如果不为零,则表示还有重入锁未释放
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;
//如果state状态为零则表示该锁可以释放
if (c == 0) {
free = true;
将锁标志线程清空
setExclusiveOwnerThread(null);
}
//如果不是,则将该state减一后重新赋值
setState(c);
return free;
}
总结:
在获取同步状态时,同步器维护⼀个同步队列,获取状态失败的线程都会被加⼊到队列中并在队列中进⾏⾃旋;移出队列(或停⽌⾃旋)的条件是前驱节点为头节点且成功获取了同步状态。在释放同步状态时,同步器调⽤tryRelease(int arg)⽅法释放同步状态,然后唤醒头节点的后继节点。《java并发艺术》
Condition的源码解析
condition中定义了等待/通知两种类型的方法,当线程调用这些方法时。需要获取到Condtion对象关联的锁。Condition对象是由Lock对象new 出来的,Condtion是依附Lock。就跟wait/notify依赖于sync锁是一样的。
condition与wait/notify的去区别
condition比wait/notify跟加灵活,他可以指定线程等待与通知
Condition的使⽤⽅式⽐较简单,需要注意在调⽤⽅法前获取锁,
condition中依靠await跟signal这两个方法来实现的。
底层依旧靠AQS来实现。
ConditionObject类是AQS的内部类,因为condition的实现需要依靠lock锁,所以再内部也是很合理的,每个condition中都有自己维护的队列。该队列是实现等待/通知的关键。
condition中的await源码
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//将当前线程放入condition中的队列中
Node node = addConditionWaiter();
//获取当前锁状态,调用了await要释放当前锁,让其他线程去尝试获取锁
long savedState = fullyRelease(node);
int interruptMode = 0;
//isOnSyncQueue判断当前线程是否为condition的头节点,如果是让该线程park,让出cpu执行权,给其他线程竞争获取锁
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//调用signal后唤醒该等待线程后,让该线程重新进入获取锁的状态
//进入获取锁的等待队列,竞争获取锁的执行权
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
//执行await方法首先先在addConditionWaiter中将当前线程添加进condition队列中
private Node addConditionWaiter() {
//这个队列的实现方式跟AQS队列差不多
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//判断该队列是否为空
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//获取当前锁状态,调用了await要释放当前锁,让其他线程去尝试获取锁
final long fullyRelease(Node node) {
boolean failed = true;
try {
long savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
condition中的signal方法
public final void signal() {
//先判断当前线程是否为获取锁的线程,如果不是则直接抛出异常。接着调用doSignal()方法来唤醒线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
//这里先从transferForSignal()方法来看,通过上面的分析我们知道
//Condition队列中只有线程一创建的一个Node节点,且waitStatue为
//CONDITION,先通过CAS修改当前节点waitStatus为0,然后执行
//enq()方法将当前线程加入到等待队列中,并返回当前线程的前置节点
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
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;
}
}
}
}