文章目录
一、 AQS的类图结构
AQS全称AbstractQueuedSynchronizer,即抽象同步队列,是一个基于先进先出(FIFO)等待队列的实现阻塞锁和同步器的框架。
下面看一下AQS的类图结构:
AQS的子类在其他类中的引用:
1. state
在AQS中维持了一个单一的共享状态state,来实现同步器同步。state用volatile修饰,保证多线程中的可见性。
-
getState():获取当前的同步状态
-
setState(int newState):设置当前同步状态
-
compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性
以上3种方法都是采用final修饰的,不允许子类重写。
相关代码如下 :
/**
* The synchronization state.
*/
private volatile int state;
/**
* Returns the current value of synchronization state.
* This operation has memory semantics of a {@code volatile} read.
* @return current state value
*/
protected final int getState() {
return state;
}
/**
* Sets the value of synchronization state.
* This operation has memory semantics of a {@code volatile} write.
* @param newState the new state value
*/
protected final void setState(int newState) {
state = newState;
}
/**
* Atomically sets synchronization state to the given updated
* value if the current state value equals the expected value.
* This operation has memory semantics of a {@code volatile} read
* and write.
*
* @param expect the expected value
* @param update the new value
* @return {@code true} if successful. False return indicates that the actual
* value was not equal to the expected value.
*/
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
2. Node
AQS是基于FIFO队列的存储结构,它是以内部类Node节点(储节点)的形式进行存储。这个等待队列是CLH同步队列。
CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),**condition队列的后续节点(nextWaiter)**如下图:
waitStatus几种状态状态:
static final class Node {
/** 共享节点模式下的节点 */
static final Node SHARED = new Node();
/** 独占模式下的节点 */
static final Node EXCLUSIVE = null;
/** 取消状态 */
// 由于超时或中断而导致当前线程(对应同步队列或条件队列中的一个节点)被取消
// CANCELLED是终态
// 被取消了的节点对应的线程永远不会阻塞,放弃竞争锁
static final int CANCELLED = 1;
/** 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行 */
// 当前节点的后继节点通过park操作被阻塞(或将要被阻塞)
// 因此当前节点在它们释放锁或被取消的时候,需要通过unpark操作唤醒它的后继节点
// 为了避免竞争(依据等待状态进行筛选,无需全部唤醒),
// 执行竞争锁的方法(acquire methods)的线程首先需要表明它们需要被唤醒,
// 如果竞争锁失败,它们就会被阻塞,等待被唤醒
// 是否需要被唤醒,其实是记录在当前节点的前驱节点的等待状态中
// 因此SIGNAL表示后继节点需要被唤醒,这一点非常重要!!
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
// 当前线程对应的节点处于条件队列中
// 在当前线程对应的节点转移到同步队列之前,同步队列不会使用当前线程对应的节点
// 在当前线程对应的节点转移到同步队列的时候,等待状态会首先被设置为0
static final int CONDITION = -2;
/**
* 下一次共享式同步状态获取将会无条件地传播下去
*/
static final int PROPAGATE = -3;
/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;
/**
* 同步队列中的前驱节点
*/
volatile Node prev;
/**
* 同步队列中的后继节点
*/
volatile Node next;
/**
* 获取同步状态的线程(请求锁或等待Condition的线程)
*/
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;
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
// Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) {
// Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
// Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
二、AQS同步队列(CLH)
1. AQS同步原理
CLH(Craig, Landin, and Hagersten locks) 同步队列 是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。AQS依赖它来完成同步状态state的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。
2. AQS同步器的结构—入列与出列
2.1 AQS同步器的结构
同步器拥有首节点(head)和尾节点(tail)。同步队列的基本结构如下:
图 1.同步队列的基本结构 compareAndSetTail(Node expect,Node update)
2.2 入列
同步队列设置尾节点(未获取到锁的线程加入同步队列)
同步器AQS中包含两个节点类型的引用:一个指向头结点的引用(head),一个指向尾节点的引用(tail),当一个线程成功的获取到锁(同步状态),其他线程无法获取到锁而是被构造成节点(包含当前线程,等待状态)加入到同步队列中等待获取到锁的线程释放锁。
注意:
加入队列的过程,必须要保证线程安全。否则如果多个线程的环境下,可能造成添加到队列等待的节点顺序错误,或者数量不对。因此同步器提供了CAS原子的设置尾节点的方法(保证一个未获取到同步状态的线程加入到同步队列后,下一个未获取的线程才能够加入)。
如下图: tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点。
图 2.尾节点的设置 节点加入到同步队列
代码实现可以参考:2.1 独占锁的获取 addWaiter()
2.3 出列
同步队列设置首节点(原头节点释放锁,唤醒后继节点)
同步队列遵循FIFO,头节点是获取锁(同步状态)成功的节点,头节点在释放同步状态的时候,会唤醒后继节点,而后继节点将会在获取锁(同步状态)成功时候将自己设置为头节点。设置头节点是由获取锁(同步状态)成功的线程来完成的,由于只有一个线程能够获取同步状态,则设置头节点的方法不需要CAS保证,只需要将头节点设置成为原首节点的后继节点 ,并断开原头结点的next引用。
如下图,设置首节点:
图 3.首节点的设置
代码实现可以参考:2.2 独占锁的释放
三、 AQS锁
1. AQS支持的锁的类别
-
独占锁:锁在一个时间点只能被一个线程占有。根据锁的获取机制,又分为“公平锁”和“非公平锁”。等待队列中按照FIFO的原则获取锁,等待时间越长的线程越先获取到锁,这就是公平的获取锁,即公平锁。而非公平锁,线程获取的锁的时候,无视等待队列直接获取锁。ReentrantLock和ReentrantReadWriteLock.Writelock是独占锁。
-
共享锁:同一个时候能够被多个线程获取的锁,能被共享的锁。JUC包中ReentrantReadWriteLock.ReadLock,CyclicBarrier,CountDownLatch和Semaphore都是共享锁。
2. 基于AQS实现锁(独占与共享模式)
AQS中没有实现任何的同步接口,所以一般子类通过继承AQS以内部类的形式实现锁机制。一般通过继承AQS类实现同步器,通过getState、setState、compareAndSetState来监测状态, AQS定义了的一些模板方法,子类只需要覆盖这几个方法即可注意着五个方法并不是抽象方法,因此子类并不是必须全部覆盖这些方法。JUC中实现这些方法的子类有:
-
tryAcquire():独占方式。尝试获取资源,成功则返回true,失败则返回false。
-
tryRelease():独占方式。尝试释放资源,成功则返回true,失败则返回false。
-
tryAcquireShared():共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
-
tryReleaseShared():共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
- isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
注意:AQS用到了模板方法设计模式。
模板方法模式: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
独占式:同一时刻仅有一个线程持有同步状态,如ReentrantLock。又可分为公平锁和非公平锁。
- 公平锁: 按照线程在队列中的排队顺序,有礼貌的,先到者先拿到锁。
- 非公平锁: 当线程要获取锁时,无视队列顺序直接去抢锁,不讲道理的,谁抢到就是谁的。
共享式:多个线程可同时执行,如Semaphore/CountDownLatch等都是共享式的产物。
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。
2.1 独占锁的获取
参考: https://www.cnblogs.com/200911/p/6031350.html
调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,即线程获取同步状态失败后进入同步队列,后续对线程进行中断操作时,线程不会从同步队列中移除。
(1) 当前线程实现通过tryAcquire()方法尝试获取锁,获取成功的话直接返回,如果尝试失败的话,进入等待队列排队等待,可以保证线程安全(CAS)的获取同步状态。
(2) 如果尝试获取锁失败的话,构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node node,int args)方法,将节点加入到同步队列的队列尾部。
(3) 最后调用acquireQueued(final Node node, int args)方法,使该节点以死循环的方式获取同步状态,如果获取不到,则阻塞节点中的线程。acquireQueued方法当前线程在死循环中获取同步状态,而只有前驱节点是头节点的时候才能尝试获取锁(同步状态)( p == head && tryAcquire(arg))。
原因:
1.头结点是成功获取同步状态的节点,而头结点的线程释放锁以后,将唤醒后继节点,后继节点线程被唤醒后要检查自己的前驱节点是否为头结点。
2.维护同步队列的FIFO原则,节点进入同步队列以后,就进入了一个自旋的过程,每个节点(后者说每个线程)都在自省的观察。
2.1.1 acquire()
#java.util.concurrent.locks.AbstractQueuedSynchronizer
/**
* 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) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
2.1.2 tryAcquire()
尝试获取锁:tryAcquire方法:如果获取到了锁,tryAcquire返回true,反之,返回false。
#java.util.concurrent.locks.ReentrantLock.FairSync
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//当前锁没被占用
if (!hasQueuedPredecessors() &&//1.判断同步队列中是否有节点在等待
compareAndSetState(0, acquires)) {
//2.如果上面!1成立,修改state值(表明当前锁已被占用)
setExclusiveOwnerThread(current);//3.如果2成立,修改当前占用锁的线程为当前线程
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
</