AbstractQueuedSynchronizer#acquire方法是获得锁的入口方法,其主要的设计理念就是使用乐观锁的方式设置 state 的属性值,如果设置失败或者当前有线程持有锁,那么为当前线程创建一个节点并添加到双链表中。下面先分析该方法。
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) {
// 通过调用 tryAcquire 方法尝试设置 state 属性值,如果返回 true,表示持有锁,那么该方法结束
// addWaiter 方法将为当前线程创建一个节点,并添加到链表的尾部,使用的是乐观锁的方式,如果链表没有初始化,那么会先初始化
// 调用 acquireQueued 方法尝试使用自选锁的方式获得锁,牺牲 CPU 避免调用 LockSupport#park 方法
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
AbstractQueuedSynchronizer#acquire 方法步骤主要为:
- 尝试通过 tryAcquire 方法设置 state 的值,使用的是乐观锁的方式。
- 由于是公平锁,所以如果此时恰巧有线程释放了锁,那么会判断此时双链表中是否有节点在等待锁(hasQueuedPredecessors方法,该方法实现很有意思,后面会单独分析代码),若有或乐观锁设置 state 变量值失败,那么直接返回 false;
- 判断当前线程是否为持有锁的线程,若是,则返回 true;
- 返回 false;
- 调用 addWaiter 方法为当前线程创建一个节点,并添加到链表尾部,同样使用的是乐观锁设置值。
- 调用 acquireQueued 方法尝试使用自选锁的方式获得锁,牺牲 CPU 避免调用 LockSupport#park 方法。
- 判断自己是否为链表中的第一个等待节点,如果是,那么再次调用 tryAcquire 方法(第一次自旋)尝试获得锁,若获得锁成功,将 head 指向当前节点,为了 GC,将原 head.next 置为 null,该方法结束返回 false。
- 如果调用 tryAcquire 方法失败,那么调用 shouldParkAfterFailedAcquire 方法根据上一个节点的状态(waitStatus == -1)判断是否需要将线程挂起,由于这个属性的初始值为 0,所以此时调用该方法会将上一个节点的 waitStatus 设置为 -1 并返回 false,由于该方法是一个死循环,所以重新回调上一步(第二次自旋)。
- 如果 shouldParkAfterFailedAcquire 方法返回 true,表示那么会调用 parkAndCheckInterrupt 方法将线程挂起(通过 LockSupport#park 方法,后面会分析),该方法返回 true 表示当前线程被中断,返回 false 表示没有被中断,如果返回 true 会调用 selfInterrupt 方法完成中断。
总结 tryAcquire 方法返回 true 的情况就是 state 为 0,并且没有等待的节点,并且使用乐观锁更新 state 值成功;或者当前线程是持有锁的线程。
可以打断被 park 方法挂起的线程的方式:
- LockSupport#unpark(Thread)
- Thread#interrupt
其实AQS的实现原理就是双链表+乐观锁+自旋锁+LockSupport#park方法。
LockSupport#park
- 根据 C++ 代码中可知道,Parker对象中持有 _cond 属性(信号量),该属性决定了线程是否需要挂起(其实如果线程有中断标志也可以继续执行),如果 _cond > 0,那么表示不需要挂起,此时将 _cond 设置为 0。如果 _cond = 0,那么表示没有执行许可,此时会再判断是否有中断标志,如果有,那么 park 方法执行结束;如果 _cond = 0 又没有中断标志,那么将线程设置为阻塞状态,并挂起线程,直到有通知,会将 _cond 设置为 0,park 方法执行结束。总结步骤为:判断 _cond > 0 是否为 true,若为 true,那么表示有执行许可,将 _cond 设置为 0,park 方法执行结束返回。
- _cond > 0 为 false,判断是否有中断标志,若有,那么 park 方法执行结束。
- 如果 _cond > 0 为 false,并且没有中断标志,那么将 java线程所拥有的操作系统线程设置为 CONDVAR_WAIT 状态,等待某个条件的通知。
- 接收到通知后,将 _cond 设置为 0,park 方法执行结束。
下面是 Parker 对象的 C++ 代码:
class PlatformParker : public CHeapObj {
protected:
//互斥变量类型
pthread_mutex_t _mutex [1] ;
//条件变量类型
pthread_cond_t _cond [1] ;
public:
~PlatformParker() { guarantee (0, "invariant") ; }
public:
PlatformParker() {
int status;
//初始化条件变量,使用 pthread_cond_t之前必须先执行初始化
status = pthread_cond_init (_cond, NULL);
assert_status(status == 0, status, "cond_init”);
// 初始化互斥变量,使用 pthread_mutex_t之前必须先执行初始化
status = pthread_mutex_init (_mutex, NULL);
assert_status(status == 0, status, "mutex_init");
}
}
LockSupport#unpark
调用 unpark 方法时,会将 _cond 设置为 1,唤醒线程让其继续执行。unpark 方法可以非常精准的唤醒一个线程,区别于 notify 是随机通知一个线程,而 notifyAll 则是通知所有在等待该条件的线程。
基于 park 方法和 unpark 方法的特性,我们完全可以先调用 unpark 方法,再执行 park 方法,此时不会挂起线程。
AbstractQueuedSynchronizer#release
release 方法是释放锁的入口方法,它的逻辑比较简单,就是通知双链表中第一个等待节点(head 节点是一个空节点),通知的方式就是靠 LockSupport#unpark 方法实现。
/**
* 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) {
// tryRelease 判断 state 属性值是否为 0
// 可重入锁需要等待所有调用了 lock 的地方都调用 unlock 方法,即调用多少次 lock 方法,就需要对应多少次 unlock 方法。
if (tryRelease(arg)) {
// 通知双链表中第一个等待节点的线程
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
AbstractQueuedSynchronizer#hasQueuedPredecessors
不是必须了解的,只需要知道该方法是用于判断是否有在等待的节点即可。但是其设计非常的巧妙不得不感叹。
AbstractQueuedSynchronizer#hasQueuedPredecessors方法是公平锁用于判断当前是否有在其前面的节点。先看下将节点添加到双向链表的方法 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) {
// 由于使用的是乐观锁,可能将该节点添加到尾节点失败,所以需要死循环
for (;;) {
// 当前的尾节点,如果别的线程设置成功了,那么乐观锁尝试设置尾节点会失败
Node t = tail;
// 如果为null,表示还没有初始化
if (t == null) { // Must initialize
// 乐观锁设置头节点,这是一个空节点
// 初始化后头节点和尾节点相等
// 进入第二个循环
// 这里有一个间隙可能出现head和tail不相等的情况,表明此时有一个节点正在尝试加入队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 先尝试设置当前节点的上一个节点为尾节点
node.prev = t;
// 使用乐观锁将当前节点设置为尾节点
// 如果失败,那么进入下一个循环
if (compareAndSetTail(t, node)) {
// 成功设置当前节点为尾节点,那么将老的尾节点的next指针指向新的尾节点
t.next = node;
return t;
}
}
}
}
下面看 hasQueuedPredecessors 方法返回值的情况:
- tail != head 返回 true
- tail == null 为 true,head == null 为 false,可能发生在双链表初始化的阶段,即已经创建了头节点,但是还没有把尾节点设置为头节点。
- tail == null 为 false,head == null 为 false,这种情况表示双链表中有等待的节点
- tail != head 返回 false
- tail == null 为 true,head == null 为 true,表明链表没有初始化
- tail == null 为 false,head == null 为 false,表明此时链表已经初始化了,相等表明没有在等待的节点
- tail != head 返回 true 并且 s = h.next == null 返回 true
- 就是1.1这种情况,有节点在尝试加入队列,但是还没加入
- tail != head 返回 true 并且 s = h.next == null 返回 false
- 表明此时有链表中有节点在等待,判断第一个节点的线程是不是当前线程
// 判断其前面是否有节点,保证公平获得锁
// 返回 true 表示前面有等待的节点,返回 false 表示前面没有等待的节点
public final boolean hasQueuedPredecessors() {
// 1、tail != head 返回 true
// 1)tail == null 为 true,head == null 为 false,可能发生在双链表初始化的阶段,
// 即已经创建了头节点,但是还没有把尾节点设置为头节点。
// 2)tail == null 为 false,head == null 为 false,这种情况表示双链表中有等待的节点
// 2、tail != head 返回 false
// 1) tail == null 为 true,head == null 为 true,表明链表没有初始化
// 2) tail == null 为 false,head == null 为 false,表明此时链表已经初始化了,相等表明没有在等待的节点
// 3、tail != head 返回 true 并且 s = h.next == null 返回 true
// 1)就是1的1)这种情况,有节点在尝试加入队列,但是还没加入
// 4、 tail != head 返回 true 并且 s = h.next == null 返回 false
// 1)表明此时有链表中有节点在等待,判断第一个节点的线程是不是当前线程
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
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());
}
FairSync#tryAcquire
/**
* 尝试设置 state 属性的值为 acquires,若为可重入锁,那么 state += acquires
* 如果修改状态成功
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 获取 state 变量的值
int c = getState();
// 如果为 0 表示没有线程持有锁
if (c == 0) {
// 调用 hasQueuedPredecessors 方法判断是否有节点尝试入队列,这种情况发生的情况为:持有锁的线程刚好释放了锁,由于是公平锁,所以需要判断前面是否有在等待的节点
// 如果前面没有在等待的节点,那么认为当前节点可以竞争锁,尝试使用乐观锁将state的值设置为1,如果设置成功,那么持有锁,设置当前持有锁的线程为当前线程
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {// 判断当前线程是否为持有锁的线程,这里说明它是一个可重入锁
// 持有锁数量+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}