在分析 Java 并发包 java.util.concurrent 源码的时候,少不了需要了解 AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。 下面我们开始分析通过ReentrantLock来分析AQS的源码,当然主要是通过FairSync来分析。
首先我们来看看AQS的属性有哪些?
/**
* Head of the wait queue, lazily initialized. Except for
* initialization, it is modified only via method setHead. Note:
* If head exists, its waitStatus is guaranteed not to be
* CANCELLED.
*/
// 头结点,你直接把它当做 当前持有锁的线程 可能是最好理解的
private transient volatile Node head;
/**
* Tail of the wait queue, lazily initialized. Modified only via
* method enq to add new wait node.
*/
// 尾结点
private transient volatile Node tail;
/**
* The synchronization state.
*/
// 是否持有锁的状态,=0表示没有线程持有锁,>0表示持有锁(为什么可以大于1?因为有可重入锁的存在)
private volatile int state;
以上就是AQS的四个属性
AQS通过Node这个静态内部类实现了一个双向的阻塞队列,当多线程来获取锁的时候,没有抢到锁的线程都是挂起在这个队列中。强调一下,head节点不在阻塞队列中,head节点不在阻塞队列中,head节点不在阻塞队列中。
下面我们来看看Node的属性
// 共享模式
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
// 独占模式
static final Node EXCLUSIVE = null;
/** waitStatus value to indicate thread has cancelled */
// 当前线程取消了锁争抢
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
// 官方描述的是:当前节点的后续节点需要被唤醒
static final int CANCELLED = -1;
/** waitStatus value to indicate thread is waiting on condition */
// 条件队列相关,暂时不作分析
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
// 同样略过
static final int PROPAGATE = -3;
// 当前节点的状态,包括上边四个(CANCELLED,CANCELLED,CONDITION,PROPAGATE)装填,加一个默认值0
volatile int waitStatus;
/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
// 阻塞队列的前节点
volatile Node prev;
/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
// 阻塞队列的后节点
volatile Node next;
/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
// 节点的线程本体
volatile Thread thread;
/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
// 这个是形成条件队列的属性,暂时略过
Node nextWaiter;
下面我们通过ReentrantLock来分析AQS。ReentrantLock内部通过维护的Sync来实现AbstractQueuedSynchronizer。Sync有两个实现类:FairSync(公平锁),NonfairSync(非公平锁)
ReentrantLock默认使用非公平锁,除非手动指定。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
本文主要通过FairSync。
// 第一步,调用lock()方法
final void lock() {
acquire(1);
}
// AbstractQueuedSynchronizer的acquire方法,tryAcquire方法就要看子类的实现了。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 方法的入参acquires=1
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
// 获取当前锁状态,如果是第一个现场进来,这个时候status=0,还没有线程拿到锁
int c = getState();
if (c == 0) {
// 不能直接抢锁,先要判断阻塞队列中是否已经有线程在排队了。因为是公平锁,所以每个新线程都需要入阻塞队列,先排队的先执行。
if (!hasQueuedPredecessors() &&
// 表示阻塞队列为空,可以尝试获取锁
compareAndSetState(0, acquires)) {
// 获取锁,标记锁为当前线程独占。
setExclusiveOwnerThread(current);
return true;
}
}
// 当前已经有线程获取锁了,判断一下获取锁的线程是否是当前线程,因为ReentrantLock是可重入锁
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
public final boolean hasQueuedPredecessors() {
// 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 true?head节点已经创建,并且(要么阻塞队列为空,要么阻塞队列第一个节点就是当前节点)。什么时候return false?要么head节点都没有,或者,阻塞队列不为空,并且队列中第一个节点不是当前节点。
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
public final void acquire(int arg) {
// 如果tryAcquire返回true表示拿到锁了,if判断不成立,线程继续执行自己的业务,如果没有拿到锁,则需要执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg),追加到队列中。先执行addWaiter(Node.EXCLUSIVE)方法。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 需要注意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指向老尾结点,自旋设置当前Node为新的尾结点。
node.prev = pred;
if (compareAndSetTail(pred, node)) {
// 将老尾结点的next指向新节点。形成双向的链表。当前的Node就加入阻塞队列了。
pred.next = node;
return node;
}
}
// 如果执行到这里,表示之前的尾结点为空或者自旋设置当前Node为尾结点失败。当前Node还未入队。
enq(node);
return node;
}
// 循环将当前线程入队
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
// 表示当前队列是空的,新创建一个head节点,并将head节点也设置为tail节点。注意新创建的Node的waitStatus=0。然后进入下一次循环。此时tail就不为空了。
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 下边就是将Node入队,并且设置当前Node为新的tail节点。
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 此时Node已经入队成功了。接着往下执行
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// 如果前一个节点是head节点,可以尝试获取锁,因为如果head节点是新创建的,此时head还没有Thread。
if (p == head && tryAcquire(arg)) {
// 如果获取锁成功,设置当前节点为head
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 走到这里表示获取锁失败了,线程就应该挂起在这里了。记住这里,阻塞队列中的线程是挂起在这里,当前线程被唤醒,是需要从这里继续往下执行的。shouldParkAfterFailedAcquire,返回false,则进入下一次循环,返回true,执行parkAndCheckInterrupt方法。这个方法一般都是返回false的,
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 获取前一节点的状态,
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 如果等于-1,就直接返回true,表示前一个节点是正常的,当前节点就需要挂起。
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
// 如果大于0,表示取消了锁等待。则需要往前找小于0的节点
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 等于=0,或者-2,-3。初始就是等于0的。在这个地方就是将前节点的状态修改为-1.然后从新执行循环,再判断前一节点的状态=-1了,然后当前节点就挂起了。
/*
* 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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
// 挂起当前线程,返回的是当前线程是否中断,一般都是false。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);// 线程是挂起在这里的。线程重新调起,还是从这里执行。
return Thread.interrupted();
}
以上就是ReentrantLock中公平锁加锁的代码分析。感谢各位朋友花费时间阅读!希望这篇文档对你有帮助。
参考文档:Javadoop大佬