AQS(AbstractQueuedSynchronizer)源码(一)
AbstractQueuedSynchronizer是并发编程的核心框架
ReentrantLock. ReadWriteLock. CountDownLatch等都是基于他实现的
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable
这是一个抽象类, 无法直接new出来 所以要继承并重写他的方法
需要重写的方法有:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
protected int tryAcquireShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean tryReleaseShared(int arg) {
throw new UnsupportedOperationException();
}
protected boolean isHeldExclusively() {
throw new UnsupportedOperationException();
}
AQS框架维护着两种模式, 独占模式和共享模式. 你可以任选其一实现,或者两者都实现
其中tryAcquire和tryRelease是独占模式
tryAcquireShared 和 tryReleaseShared为共享模式
我们先看一下他的独占模式实现的机制
我们可以基于ReentrantLock讲解
独占模式
ReentrantLock的Lock方法(获取锁操作)
其中他以内部类的形式继承了AQS 也就是sync是一个AQS的子类,它辅助了ReentrantLock
public void lock() {
sync.acquire(1);
}
接下来就要介绍AQS的acquire方法了
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先需要调用子类实现的tryAcquire方法(这里如果不实现就直接抛异常)
那接下来就要说一下AQS的实现机理了
AQS维护了一个int类型的变量state和一个队列(双向链表)
那ReentrantLock的公平锁是如何实现的呢?
ReentrantLock使用了独占模式,其state维护的是线程的数量值
同时ReentrantLock还保存了独占锁的线程
state一开始是0代表没有线程获取, state > 0 就代表有线程获取
state = 0的情况就让ReentrantLock保存的线程等于当前线程, 让state变为1
如果state > 0了 有其他线程抢夺资源. 注意:这里实现的是公平锁,
判断一下, 如果当前的线程和我保存的线程是一样的, 那么让state++
如果不一样的话就扔到等待队列去
队列是一种FIFO(先进先出)的数据结构, 他维护了线程的公平性,
好了回到代码
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire(arg) 的 arg 在上面的lock方法默认调用的是1 你就可以记住 arg是1
tryAcquire(arg)表示尝试获取锁, 如果获取成功返回true, 不成功返回false
因为判断的时候短路运算, 如果tryAcquire(arg)返回了true就不会执行后面的方法
否则返回false 代表当前线程没有拿到锁就应该放到等待队列中去, 同时判断一下有无响应中断机制
首先先来看一下ReentrantLock实现的tryAcquire(arg)方法, 但是这其实并不是必须的.因为这是他自己重写了实现的
protected final boolean tryAcquire(int acquires) {
//获取一下当前线程
final Thread current = Thread.currentThread();
//得到state就是上述维护的那个int值 这里是锁的当前数量(也就是可重入锁)
int c = getState();
//c=0的意思就是当前没有线程获得锁
if (c == 0) {
//如果等待队列中有元素,也就表示有人在前面排队
//所以你新进来的线程必须等待
//如果队列中没有元素了并且CAS操作状态成功(待会说)
//setExclusiveOwnerThread这个方法就设置锁的线程绑定为当前线程
//返回获取锁成功
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//否则判断一下当前线程和绑定的线程是否是同一个线程
else if (current == getExclusiveOwnerThread()) {
//这里只是把state值+1
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
//如果程序运行到这 就说明有线程获得了锁, 并且不是当前线程
//由于公平的原因就返回无法获得锁
return false;
}
CAS(CompareAndSwap)
CAS即比较并替换
一个经典问题i++这个问题在以前我们只是串行的就跑没有问题
但是在多线程情况下 可能出现脏读的情况
也就是内存的可见性和原子性的问题
i++这个操作看似一步语句, 其实包含了三步
第一步先拿到i的值
第二步 给i+1
第三步 再把i的值写回去
下列并发情况就会出现问题
第一个线程先读到内存中的值 i=1 并且给他+1
这个时候线程2先进来了,读了一下内存中的值, 这个时候线程1中还没有更改 所以线程2也读到的1
这个时候线程1, 2 都修改了i且都变成了2 但是我们的期望值应该是3
所以这个时候就用到了CAS技术
CAS有3个值 一个是内存值V(也就是期望值), 还有两个参数,A,B
CAS就是 我认为你的V应该等于A 如果相等 我把V替换成B 否则我什么也不做
所以上述的问题, 如果有两个线程获得执行权
且都进入到c == 0
线程1调用compareAndSetState(0, acquires) 这里的acquires是1
如果成功了就把state的0改成了1
这样线程2就算到了进入到了c==0 然后他调用compareAndSetState(0, acquires)方法
他期望内存值是0 可是,我们知道他已经被线程1改成了1 所以就什么也不做并返回false
好了之后的只要有CAS操作我都不用讲这么细了, 如果CAS失败就说明发生了并发问题
tryAcquire的方法还是比较好理解
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
接下来流程应该走到addWaiter(Node.EXCLUSIVE), arg)方法
private Node addWaiter(Node mode) {
Node node = new Node(mode);
//自旋添加结点
for (;;) {
Node oldTail = tail;
//如果尾结点不是空的
if (oldTail != null) {
//因为新节点要添加到尾部
//设置尾部的上一个结点为之前的尾部
node.setPrevRelaxed(oldTail);
//如果CAS操作失败了说明发生了并发问题
//就自旋去设置重新跑一次循环
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {//这里说明队列是空的,则初始化队列
initializeSyncQueue();
}
}
}
private final void initializeSyncQueue() {
Node h;
if (HEAD.compareAndSet(this, null, (h = new Node())))
tail = h;
}
接下来流程走到了acquireQueued方法
可以看到这里也用到了死循环自旋CAS操作, 这是一套标准组合拳
这个方法是尝试把线程挂起
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
//这里的node是新添加的结点
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;
}
}
先说维护队列
每个结点有几种状态,
这些状态由int类型来表示
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
CANCELLED:值为1,在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该Node的结点,其结点的waitStatus为CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
SIGNAL:值为-1,被标识为该等待唤醒状态的后继结点,当其前继结点的线程释放了同步锁或被取消,将会通知该后继结点的线程执行。说白了,就是处于唤醒状态,只要前继结点释放锁,就会通知标识为SIGNAL状态的后继结点的线程执行。
CONDITION:值为-2,与Condition相关,该标识的结点处于等待队列中,结点的线程等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
PROPAGATE:值为-3,与共享模式相关,在共享模式中,该状态标识结点的线程处于可运行状态。
这里我们先只关注SIGNAL
新创建的结点默认是为0的
也就是说 如果队列有4个 那么他的状态一定是 -1 -1 -1 0
过程走到了shouldParkAfterFailedAcquire方法
判断当前结点的前一个结点的等待状态是不是-1 如果是-1的话就可以安全的将这个挂起
如果前一个被取消了的话 需要维护队列 循环跳过所有被取消了的结点找到一个没有被取消的结点
把当前结点排到他的后面
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
//这里就是维护队列
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//尝试把当前线程设置为SIGNAL
pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
}
return false;
}
//通过LockSupport挂起线程,等待唤醒
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;
}
先将一下tryRelease尝试释放锁的方法
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
//c是当前锁-1
//判断一下当前线程如果和绑定的线程不是同一个抛异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//判断一下 锁-1的数量是不是等于0
//如果成立就把绑定线程置空
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//不管如何都设置锁的数量-1
setState(c);
//如果是锁的数量变为0了也就是释放了返回true 否则只是把锁数量-1
return free;
}
然后再看一下release操作, 如果释放锁成功, 且队列不是空的情况下调用unparkSuccessor
其实这个猜也可以猜出来就是唤醒下一个线程
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
看一下唤醒下一个线程的方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//这里先只说SINGAL
//因为等待状态为SINGAL是-1表示挂起, 等待状态为0表示运行
if (ws < 0)
node.compareAndSetWaitStatus(ws, 0);
//从队列里找出下一个需要唤醒的节点
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);
}
好了, 公平锁已经讲解完毕, 那么下面说一下非公平锁
如果你是认认真真看到这, 或者已经有了基础看到这 那么下面的应该不难理解
先看一下二者的差别
首先是公平锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
非公平锁
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
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;
}
其实这里就发现 公平的调用了是否有等待队列, 而非公平的是直接尝试进行CAS获取
也就是说公平的只要有队列就返回获取锁失败
而非公平的则是抢占式的获取锁, 失败了再进队列
如果这个时候队列有3个线程在等待
这时候又新进来了一个线程, 在运行的线程刚好结束了, 那么新进的线程就抢到了锁, 这样就实现了非公平
注意: 非公平锁是默认的