Java中的CAS
CAS:全称为Compare And Swap,比较并交换,CAS 是现代操作系统,解决并发问题的一个重要手段。一个CAS涉及到了下面的操作:
假设内存中的原数据V,旧的预期值A,需要修改的新值B
1、比较 A 与 V 是否相等(比较)
2、如果比较相等,将 B 写入 V(交换)
3、返回操作是否成功
当多个线程对资源进行CAS操作时,只有一个线程能够成功,但是不会阻塞其他线程,其他线程收到的是操作失败信号。
以AtomicInteger为例,打开其源码可以发现,Java的CAS操作是通过sun包下Unsafe类实现的,Unsafe类中的方法都是native方法,由JVM本地实现。
AQS实现原理及源码分析
1、概括
AQS,也即是AbstractQueuedSynchronizer,队列同步器,AbstractQueuedSynchronizer类是一个juc包下的抽象类,以继承的方式使用,为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。此类的设计目标是成为依靠单个原子 int 值来表示状态的大多数同步器的一个有用基础。
根据源码中对AbstractQueuedSynchronizer类的介绍,我们可以了解到以下信息:
①、AQS其实就是一个可以给我们实现锁的框架
②、内部实现的关键是:先进先出的队列、state状态
③、定义了内部类ConditionObject
④、拥有两种线程模式:独占模式和共享模式
⑤、在LOCK包中的相关锁(常用的有ReentrantLock、 ReadWriteLock)都是基于AQS来构建
⑥、一般称AQS为同步器
查看AbstractQueuedSynchronizor类的源码,发现AQS最主要的变量有以下三个:
private transient volatile Node head;
private transient volatile Node tail;
private volatile int state; //表示当前有多少线程获取了锁,此变量是共享的,对于互斥锁来说state<=1
AQS实现的内部依赖是一个FIFO的双端队列,当一个线程获取同步状态失败后,AQS会将此线程及其等待状态等信息构成一个Node加入到同步队列尾部,并且阻塞此线程,当其他线程同步状态释放时,会唤醒同步队列的头节点,AQS的结构示意图如下:
AQS内部通过
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return STATE.compareAndSet(this, expect, update);
} //是原子性的修改,采用CAS实现
上面的方法修改state。
2、AQS源码分析
AQS内部Node类部分常量基变量
/** 表示节点处于共享模式 */
static final Node SHARED = new Node();
/** 标记节点处于独占模式 */
static final Node EXCLUSIVE = null;
/** waitStatus的值之一,表示线程被取消了 */
static final int CANCELLED = 1;
/** waitStatus的值之一,表示后继线程需要唤醒 */
static final int SIGNAL = -1;
/** waitStatus的值之一,表示线程正等待某一等待条件 */
static final int CONDITION = -2;
/** waitStatus的值之一,表示下个acquireShared方法应该无条件地???*/
static final int PROPAGATE = -3;
/**
* 节点的等待状态,以下值的一种:
* SIGNAL: 节点的继任节点是(或者将要成为)BLOCKED状态(例如通过LockSupport.park()操作),因此一个节点一旦被释放(解锁)或者取消就需要唤醒(LockSupport.unpack())它的继任节点。
* CANCELLED: 节点操作因为超时或者对应的线程被interrupt。节点不应该留在此状态,一旦达到此状态将从CHL队列中踢出。
* CONDITION: 表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。
* PROPAGATE: 一个releaseShared 应该扩散到其他节点。设置在doReleaseShared方法(只是头结点)中为了确保扩散继续,即使其他操作已经开始执行。
* 0: 正常状态
*
* 非负值意味着节点不需要被唤醒。
*
* 这个值被初始化为0,可以使用CAS修改它。
*/
volatile int waitStatus;
从ReentrantLock的源码入手来深入理解下AQS的实现
AQS一般是以继承的方式使用的,同步组件内部组合一个继承了AQS的子类:Sync
Sync类是一个抽象类,其实现类有两个:FairSync和NonfairSync,分别对应着公平锁和非公平锁。
ReentrantLock类提供了下面的构造函数来确定使用公平锁或是非公平锁:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
获取锁的过程
NonfairSync的lock()实现如下:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
lock()方法首先通过CAS尝试将AQS的state从0修改为1(compareAndSetState()方法上面提到过),如果成功,则将占用锁的线程设置为当前线程。如果CAS操作未成功,说明state不等于0,继续执行acquire(1),这个操作由AQS提供。
acquire()具体实现如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire方法尝试获取锁,如果成功就返回,如果不成功,则把当前线程和等待状态信息构适成一个Node节点,并将结点放入同步队列的尾部。然后为同步队列中的当前节点循环等待获取锁,直到成功。
tryAcquire(arg)在NonfairSync中的实现:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
可以看到,首先是获取state,然后下面的if操作和lock()方法一样,是为了以最简单的方式获得锁。
如果state不为0,再看当前线程是不是锁的owner,如果是owner, 则尝试将状态值增加acquires,如果这个状态值越界,抛出异常;如果没有越界,则设置后返回true。在一定程度上解释了非公平和重入。
如果状态不为0,且当前线程不是owner,则返回false。
回到acquire()方法,tryAcquire返回false,接着执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)),先看下addWaiter(Node.EXCLUSIVE),这个方法创建结点并入队,addWaiter()源码如下:
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
addWaiter返回了插入的节点,作为acquireQueued方法的入参
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
if (interrupted)
selfInterrupt();
throw t;
}
}
node.predecessor()返回的是node的前置节点,也就是addWaiter方法返回的节点的前置节点,在这里是head节点,所以p==head成立,进而进行tryAcquire操作,即争用锁, 如果获取成功,则进入if方法体,看下接下来的操作:
- 将node设置为头节点。
- 将node的前置节点设置的next设置为null。
从上面的分析可以看出,只有队列的第二个节点可以有机会争用锁,如果成功获取锁,则此节点晋升为头节点。对于第三个及以后的节点,if (p == head)条件不成立,首先进行shouldParkAfterFailedAcquire(p, node)操作(争用锁失败的第二个节点也如此), 来看下源码:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* 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.
*/
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire方法是判断一个争用锁的线程是否应该被阻塞。它首先判断一个节点的前置节点的状态是否为Node.SIGNAL,如果是,锁释放的时候,应当通知它,所以它可以安全的阻塞了,返回true。
如果前节点的状态大于0,即为CANCELLED状态时,则会从前节点开始逐步循环找到一个没有被“CANCELLED”节点设置为当前节点的前节点,返回false。在下次循环执行shouldParkAfterFailedAcquire时,返回true。这个操作实际是把队列中CANCELLED的节点剔除掉。
如果shouldParkAfterFailedAcquire返回了true,则会执行:“parkAndCheckInterrupt()”方法,它是通过LockSupport.park(this)将当前线程挂起到WATING状态,它需要等待一个中断、unpark方法来唤醒它,通过这样一种FIFO的机制的等待,来实现了Lock的操作。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
释放锁的过程
通过ReentrantLock的unlock方法来看下AQS的锁释放过程:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
unlock调用AQS的release()来完成, AQS的tryRelease方法由具体子类实现。tryRelease返回true,则会将head传入到unparkSuccessor(Node)方法中并返回true,否则返回false。首先来看看Sync中tryRelease(int)方法实现,如下所示:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
这个动作可以认为就是一个设置锁状态的操作,而且是将状态减掉传入的参数值(参数是1),如果结果状态为0,就将排它锁的Owner设置为null,以使得其它的线程有机会进行执行。
在排它锁中,加锁的时候状态会增加1(当然可以自己修改这个值),在解锁的时候减掉1,同一个锁,在可以重入后,可能会被叠加为2、3、4这些值,只有unlock()的次数与lock()的次数对应才会将Owner线程设置为空,而且也只有这种情况下才会返回true。
法unparkSuccessor(Node)中,就意味着真正要释放锁了,它传入的是head节点(head节点是占用锁的节点),看下源码:
private void unparkSuccessor(Node node) {
/*
* 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)
node.compareAndSetWaitStatus(ws, 0);
/*
* 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 p = tail; p != node && p != null; p = p.prev)
if (p.waitStatus <= 0)
s = p;
}
if (s != null)
LockSupport.unpark(s.thread);
}
内部首先会发生的动作是获取head节点的next节点,如果获取到的节点不为空,则直接通过:“LockSupport.unpark()”方法来释放对应的被挂起的线程,这样一来将会有一个节点唤醒后继续进入循环进一步尝试tryAcquire()方法来获取锁。