1 概述
AQS是一个基于先进先出的等待队列来实现的锁和同步器这么一个框架。
发现一个很好的源码学习工具的网站 https://codecommenter.cn,可以记录自己添加的注释,进行源码学习。里面一些功能需要自己发现,并没有引导页面,满足你的探索欲。
2 从ReentrantLock加锁的方法开始分析源码
// ****************************************************************************************
// *** ReentrantLock公平锁的lock
// ****************************************************************************************
/*
* java.util.concurrent.locks.ReentrantLock.FairSync#lock
*
* ReentrantLock公平锁的lock方法
*/
final void lock() {
// 上来调用acquire方法
acquire(1);
}
这里就 是直接调用了AQS里的acquire方法,这就是为什么ReentrantLock是基于AQS实现的,它自己上来就调人家的方法。
/**
* java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
*
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
/*
* acquire是AQS的方法,该方法没有自己的实现逻辑,而是通过调用其它的方法来
* 这个方法做了三件事
* 1 tryAcquire
* 试着抢一下锁
* 抢到: 返回
* 没抢到: 执行第二件事
* 2 addWaiter 把自己封装成Node对象,并将当前线程加入到队列尾部
* 3 acquireQueued 把自己加到队列并不是目的,在队列里,是要等待锁资料的。所以第三件事就是当自己在队列里的时候,侍机抢一下锁资源。
*/
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
/*
* 只有线程被中断的时候,acquireQueued才会返回true,因此也才会执行selfInterrupt()方法
* 如果线程没有被中断,在acquireQueued里排上队,总有一天会获取到锁,最终返回false
* 这个方法就消无声息地执行完了
*/
selfInterrupt();
}
tryAcquire是AQS里定义的方法,但在AQS里,该方法默认实现是恒抛出了一个异常,意味着该方法的逻辑应该由实现类去实现。这是当然的,因为应该怎样获取锁,本来就应该是实现类自己去做的。比如现在的ReentrantLock来说,你觉得对state做怎样的操作后,就表示获取了锁,当然是你ReentrantLock自己去决定了,我给你提供一个这样的变量就很好了。
在ReentrantLock里,把state从0变成1,表示获取了锁,如果重入的话,很次对state都加1,这是ReentrantLock里对state的操作。还有不是这样的?读写锁就不是这样。
/**
*
* AQS里的tryAcquire方法
* java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire
*
*
* Attempts to acquire in exclusive mode. This method should query
* if the state of the object permits it to be acquired in the
* exclusive mode, and if so to acquire it.
*
* <p>This method is always invoked by the thread performing
* acquire. If this method reports failure, the acquire method
* may queue the thread, if it is not already queued, until it is
* signalled by a release from some other thread. This can be used
* to implement method {@link Lock#tryLock()}.
*
* <p>The default
* implementation throws {@link UnsupportedOperationException}.
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return {@code true} if successful. Upon success, this object has
* been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
/*
* java.util.concurrent.locks.ReentrantLock.FairSync#tryAcquire
*
*
* ReentrantLock里公平锁对tryAcquire的实现
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*
* c == 0 说明这会没有线程持有锁,但这时并不能就放心地去拿锁了
* 因为就在判断完 c == 0 以后,当前线程还没来得及拿锁(将state由0变为1),可能有其它线程
* 就抢先拿了锁,修改了state的值
* 要不怎么说是并发呢
*/
/*
* hasQueuedPredecessors用来判断当前是否已经有线程在排队
* 如果这个方法返回true,说明已经有线程在排队了,那自己肯定也只有排队了
* 因为是公平锁嘛
*
* 自己刚判断完c==0,这里就已经排上队了,说明当前线程判断完c==0后,可能休息了很长很长时间
* 这个世界已经变得跟以前不一样了。
*/
if (!hasQueuedPredecessors() &&
// 如果没有人在排队,那我刚判断完c==0,现在还没有纯种拿着锁,那我就赶快抢一把呗
compareAndSetState(0, acquires)) {
/*
* 进入到if语句块,说明compareAndSetState方法执行成功了,当前线程抢到了锁
* 于是把AQS里表示哪个线程持有锁的变量,赋值成自己这个线程
*/
setExclusiveOwnerThread(current);
return true;
}
}
// c != 0,说明当前有线程持有锁呢正,那我判断一下这个锁是不是我自己拿着呢,即当前线程
else if (current == getExclusiveOwnerThread()) {
/*
* 发现当前拿着锁的线程是我自己,说明我是重入操作,这时候把state+1就OKAY了
* 不过需要注意的是,state是一个int类型的变量,这是有最大值(2147483647)的
* 如果一直重入一直加1,可能会超过这个最大值,所有判断一下有没有越界
*/
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
如果tryAcquire方法返回 true,也就是说抢到了锁,那么acquire方法里if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
这一行判断就结果了,抢到锁直接返回。
如果tryAcquire返回false,说明没抢到锁,这时就要把当前线程封装成Node对象,去排队。然后在队列里等待被唤醒,待机再去抢一下锁。addWaiter方法就是把自己封装成Node对象去排队的逻辑。
加入到队列后,再执行acquireQueued方法,这个方法里有个死循环,在这个循环里排队的线程会被重复唤醒,再挂起,再唤醒,再挂起。直到线程抢到锁。
/**
* java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter
*
*
* 如果tryAcquire失败,会执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),即要去排队了。
* 这个addWaiter就是把当前线程封装成一个Node对象
*
* Creates and enqueues node for current thread and given mode.
*
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new 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
/*
* 这个是自己加入到队列之前的队尾,如果自己加入到了队尾,那么之前那个队尾就是上一个节点
* 了,可能是因为这个所以把这会这个队尾变量名叫做是pred
*/
Node pred = tail;
// 如果现在这个队尾不是null,
if (pred != null) {
/*
* 在这里把自己的上一个节点指向之前的那个队尾
* 不过这里并不能高兴得太早,这里只是先指了一下,并没有成定论呢。因为在此期间,可能已经有别的线程成了队尾,这里那个新的队尾的node.prev也是上面刚说到的这个队尾,即pred变量。
* 如果这里的操作是让上一个队尾的next马上就指向自己,那就坏了事了,因为自己还没有真正地成为新的队尾呢
*/
node.prev = pred;
/*
* 还是刚才说到的原因,因为有并发存在,所以想要真正地成为队尾,即抢一下占住队尾位置才行。
*/
if (compareAndSetTail(pred, node)) {
/*如果抢到了,这时可以放心地让之前的那个队尾的node.next指向自己了*/
pred.next = node;
return node;
}
}
```
/*
* 如果 pred != null 判断是false,说明现在还没有队尾,那么自己就要抢一下队尾的位置
* 或者在上面if语句块里,自己抢队尾失败,也会走到这里
* 即使在if语句块里抢队尾位置时没抢到,那也不能就这么算了,不管怎样,到头来都是要把自己加到队尾里去了。
* 这就要看一下enq里到底是个什么逻辑了
*/
enq(node);
return node;
}
/**
* java.util.concurrent.locks.AbstractQueuedSynchronizer#enq
*
* 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) {
/*
* 这里是个死循环,铁了心要把自己当前线程(已经封装成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
*
* 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;
for (;;) {
/*
* 能进入到这个方法,说明自己已经在队列里了(当前,是针对ReentrantLock来说的)
* 这里就看一下自己的上一个节点是不是头节点,如果是,就试着抢一下锁。
* 为什么发现自己的上一个节点是头节点,就要试着去抢一下锁呢?
* 这个问题可以从反面去想一个: 现在我自己(当前线程)已经加入到队列里了,但我总不能死在队列里吧,我总要找个
* 机会去抢一下锁,抢到锁后赶紧离开这。那这个机会定在什么时候比较好呢?
* 假设这个机会定在队尾,就是说发现自己是队尾的时候,就去抢一下锁。那可好了,如果一直有新的线程加入到队列里,那么靠前的
* 线程就可能永远拿不到锁了。
* 假设这个机会定在倒数第二个位置,倒数第三个,倒数第四个... ...都不合适,都会发生一样的问题。
* 那就好了,那就把这个机会定在,如果发现自己是正数第二个,即自己的上一个节点是头节点的时候,就去抢锁,这就合适了。
*
* 如果自己不是第二个节点,那自己就会去挂起线程了。别忘了,这里是个死循环,如果有一天自己被其它线程唤醒,也还是在这个死循环里。
* 这时会再次判断 if (p == head && tryAcquire(arg)) 一直到前面线程都执行完走了,到自己成为第二个节点,就有机会去试着抢一下锁了。
*/
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/*
* 【Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link #tryAcquire} until success.】
* 上面是acquire方法的一段doc注释,说自己没有抢到锁的情况下,会把自己加到队列里,然后重复地去 阻塞-解阻塞-调tryAcquire 阻塞-解阻塞-调tryAcquire
* 一直到获取锁成功为止。
* 下面这个就是重复地去 阻塞-解阻塞-调tryAcquire
*
* 上面发现自己还不是第二个节点,于是在这里阻塞了。有一天其它线程把自己唤醒了,于是在死循环里再次执行 if (p == head && tryAcquire(arg)) -- 完成了一次阻塞-解阻塞-调tryAcquire
* 如果这一次成功了,就结束,如果没成功,于是在这里又阻塞了,又到有一天其它线程把自己唤醒,于是在死循环里再次执行 if (p == head && tryAcquire(arg)) -- 又完成了一次阻塞-解阻塞-调tryAcquire
* 一直到有一天自己获取了锁为止
*
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
/*
* try语句块里是个死循环,所以只有程序发生异常时,才会走到这里来。
*/
if (failed)
cancelAcquire(node);
}
}
3 释放锁操作
/**
* 释放锁操作
* java.util.concurrent.locks.ReentrantLock#unlock
*
* Attempts to release this lock.
*
* <p>If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
把关键信息写在了注释里
比较重要的是线程的唤醒逻辑。
/**
* 在AQS里
* java.util.concurrent.locks.AbstractQueuedSynchronizer#release
*
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
/*
* 如果tryRelease返回true,说明释放锁已经释放干净,这时就要去唤醒其它线程了
*/
Node h = head;
if (h != null && h.waitStatus != 0)
// 去唤醒其它线程 successor是继承人的意思
unparkSuccessor(h);
return true;
}
return false;
}
/**
* 和tryAcquire方法相似,tryRelease方法在AQS里也是直接抛出了一个异常
* 也就是说这期望实现在自己在tryRelease。当前这很合理,在获取锁时,是实现类自己的逻辑,那释放
* 锁当然也应该由实现类自己来释放了。
*
*
* Attempts to set the state to reflect a release in exclusive
* mode.
*
* <p>This method is always invoked by the thread performing release.
*
* <p>The default implementation throws
* {@link UnsupportedOperationException}.
*
* @param arg the release argument. This value is always the one
* passed to a release method, or the current state value upon
* entry to a condition wait. The value is otherwise
* uninterpreted and can represent anything you like.
* @return {@code true} if this object is now in a fully released
* state, so that any waiting threads may attempt to acquire;
* and {@code false} otherwise.
* @throws IllegalMonitorStateException if releasing would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
/**
* java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
*/
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
/*
* 来释放锁了,结果发现现在持有锁的线程不是当前线程
* 为什么用这样?
* 正常情况下当然不会这样,不过想象一下,我们一不获取锁的情况下能不能调lock.unlock()方法,当然是可以的;或者在
* 释放时多调了几次lock.unlock(),所以
* 当出现以下编码时:
1 ->
ReentrantLock lock = new ReentrantLock();
lock.unlock();
2 ->
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock.unlock();
lock.unlock();
* 这时 Thread.currentThread() != getExclusiveOwnerThread() 就会是true
*/
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 如果释放干净了,就返回true,把持有锁的当前线程的标识置为null,之后就会有其它线程去抢锁
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
/**
* 唤醒继承人的逻辑
* java.util.concurrent.locks.AbstractQueuedSynchronizer#unparkSuccessor
*
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
private void unparkSuccessor(Node node) {
/*
* 这里的形参node就是当前正持有锁的线程,也是head变量对应的线程
*
* 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;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* traverse: 遍历的意思
*
* 有很多说法在唤醒线程时,是从后向前找可唤醒的节点。这个问题被广泛讨论,以至于会误认为在唤醒线程时,只是从后向前找这样。
* 其实这是片面的正确,在从后前找之前,会先看看自己的下一个节点是不是可唤醒(which is normally just the next node)。如果可以,当然就是直接唤醒最好了,何必还从
* 后向前找呢?
* 如果第二个节点不可唤醒(But if cancelled or apparently null.即第二个节点是null,或waitStatus是cancelled),这时候就从后向前找可唤醒的节点。
* 倒不是说为什么从后向前找,而是只能从后向前找了。因为第二个节点是null,或waitStatus是cancelled,这时不从后向前找还能怎样呢,还有什么别的办法吗?
*
* But if cancelled or apparently null,traverse backwards from tail to find the actual non-cancelled successor
* (但是如果是cancelled状态或是为null,则从后向前遍历,以此来找到一个直接的非cancelled的继承人)
*
* 这里的这个But好像有那么一点无奈不觉得吗?
*
* 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;
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);
}
Over