1.AQS是什么
AbstractQueuedSynchronizer
这是一个采用模板设计模式的抽象类,idea里面alt+7可以看到他的类结构,两个内部类Node,ConditionObject
public修饰并且有final修饰的都是模板方法,
protected修饰的没有final修饰的都抛出了一个UnsupportedOperationException();说明这些都是需要子类自己实现的流程方法
idea选中AbstractQueuedSynchronizer类ctrl+T我们看到并发包里不少类都用到了他,我们以ReentrantLock为例,这是一个可重入锁
他是用一个内部类Sync去继承的AbstractQueuedSynchronizer,内部类最大的作用就是解决了java单继承的问题(虽然这里不存在这个问题)
模板方法:
独占式获取
accquire()
acquireInterruptibly()
tryAcquireNanos()
共享式获取
acquireShared()
acquireSharedInterruptibly()
tryAcquireSharedNanos()
独占式释放锁
release()
共享式释放锁
releaseShared()
需要子类覆盖的流程方法
独占式获取 tryAcquire()
独占式释放 tryRelease()
共享式获取 tryAcquireShared()
共享式释放 tryReleaseShared()
这个同步器是否处于独占模式 isHeldExclusively()
AQS中的数据结构-节点和同步队列
竞争失败的线程会打包成Node放到同步队列,Node可能的状态里(waitStatus):
CANCELLED = 1:线程等待超时或者被中断了,需要从队列中移走
SIGNAL = -1:后续的节点等待状态,当前节点,通知后面的节点去运行
CONDITION = -2 :当前节点处于等待队列
PROPAGATE = -3:共享,表示状态要往后面的节点传播
0, 表示初始状态
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
通过上面这段静态代码块里我们可以知道,内部类Node里面维护了两个值,waitStatus和 next,外层维护了三个分别是state,头结点head,尾结点tail
我们从ReentrantLock实现的Lock里的 lock(),lockInterruptibly(),tryLock(),unlock(),newCondition();开始看起
lock()里由内部类的Sync实现,Sync又分了FairSync和NonFairSync,我们看看FairSync的lock实现
lock()
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
我们先不看模板方法里的acquireQueued(addWaiter(Node.EXCLUSIVE), arg),我们看看ReentrantLock里他重写的tryAcquire(1)
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;
}
分析上面的代码只做了两件事把维护的外层state使用CAS从0修改为1,当前拿到独占锁的线程exclusiveOwnerThread赋值为当前线程
一会再看hasQueuedPredecessors()做了什么事,如果拿到的state==0,那么久尝试CAS修改为1,成功了设置当先线程为exclusiveOwnerThread,如果state不为0,再判断当前线程是不是exclusiveOwnerThread,
如果是,就把state+=1,这里不用CAS去加是因为当前线程就是拿到独占锁的线程,state+=1就是实现重入锁的关键,解决了同一个线程里锁嵌套的问题;
我们回过头看看hasQueuedPredecessors();
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 h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
我们从他维护的五个值里大概可以知道,AQS是把多个线程去争夺这个锁的时候,第一个进来的线程拿到锁后把state修改为>=1的值,然后exclusiveOwnerThread设置为自己,可能把自己封装一个Node放到head,然后第二个线程进来发现state是1
就把自己封装成Node,放到tail,tail.pre = head, head.next = tail,这样....后面来的线程也是封装成Node往后面加....这个只是我们的大致预感,这块的实现就是由acquire(ing arg)模板方法里后面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)实现的.
继续看hasQueuedPredecessors(),这里就是判断头结点尾结点不相等,并且头结点的下一个为空,或者下一个节点的线程不是本线程,就是说链表里不止一个node并且头结点的下一个为空或者头结点的下一个不是本线程?大概就是判断链表里面有没有排队的线程
假设有就不会直接去抢着去拿这个锁,而直接返回发false了,这里我们看的是FairSync的lock实现里面的,我们对比下NonFairSync的lock实现.
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;
}
对比发现就是少了hasQueuedPredecessors()的判断
所以我们知道,公平锁就是我一个新线程去去试图拿锁的时候,需要去检测AQS的链表里面有没有排队的线程,如果有,自己就老老实实去排队,不去在这个时候去抢这个锁(state置为1,exclusiveOwnerThread设置成自己线程);
而非公平锁的就不去检测有没有其他线程排队,只要发现现在我拿到的状态是0,我就先去抢1,抢不到去求返回false了老老实实去排队,抢到了就直接拿去用了
ok,假设我们这里没有拿到锁(cas修改1失败),返回了fasle,我们看看模板方法里的acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
先看addWaiter(null),这里就是刚刚说的封装一个Node,node.nextWaiter=null,node.thread = thread;拿到维护的尾结点tail,尝试把node加成尾结点,成功了就返回node,不然就进入enq(node);
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
这个就是经典的CAS自旋锁,上一篇的并发编程CAS里实现线程安全的自增就是类似,这里去保证线程安全的加入尾结点成功;
我们接着看acquireQueued(tail), 1))
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里自旋里判断刚刚排队的tail,他的前节点是不是head节点,如果是的,他就去尝试拿锁tryAcquire(1),成功了就把自己变成head,然后返回false;
前节点不是头结点或者拿锁失败了,先看看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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
这里其实可以理解成后面&& parkAndCheckInterrupt()的执行条件,parkAndCheckInterrupt()里面就是LockSupport.park(this);
这个LockSupport.park(this)我们在并发编程之线程创建的方式和常用方法里使用过,就是让当前的线程挂起;
这个方法里有返回值,return Thread.interrupted();这个感觉应该是另一个带interrupt的方法要用到的.
我们前面说过竞争失败的线程会打包成Node放到同步队列,Node可能的状态里(waitStatus):
CANCELLED = 1:线程等待超时或者被中断了,需要从队列中移走
SIGNAL = -1:后续的节点等待状态,当前节点,通知后面的节点去运行
CONDITION = -2:当前节点处于等待队列
PROPAGATE = -3:共享,表示状态要往后面的节点传播
0, 表示初始状态
这里,node的前节点如果waitStatus是SIGNAL,就直接返回true,让node进入挂起,如果前节点是CANCELLED,就需要从链表里面去除掉他,就有了node.prev = pred = pred.prev;,
就是把node的前节点指向了前节点的前节点最后返回false然后接着自旋重新尝试拿锁去;
至此lock()结束
unlock();
再来看看unlock();
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;
}
算了,不写了,unlock就是把state-=1;如果减到0了,说明该线程要释放锁了,没减到0就是该线程重入锁后面还需要被释放,减到0后获取头结点head,head不为空并且waitStatus != 0就 unparkSuccessor(h);
这个方法里就是先把head的waitStatus置为初始状态0,然后找head的next节点,如果下一个节点为null或者是waitState是CANCELLED状态,就从tail开始往前找到最前面waitState不是CANCELLED的节点,把这个节点唤醒
unlock()结束
ConditionObject
上面的lock和unlock操作的是AQS里维护的head和tail组成的链表,我们叫它AQS里的同步队列
AQS里ConditionObject的await和signal则是AQS下Node里维护的firstWaiter,lastWaiter组成的链表,我们叫它AQS里的等待队列,我们可以new多个ConditionObjcet来创建多个等待队里
ConditionObjcet也会去操作同步队列里的数据,await的时候把当前线程封装成一个Node放到等待队列里,然后把同步队列里的head(当前拿到锁的node)释放锁,然后挂起并唤醒同步队列里的下一个node里的线程(就是unlock里的tryRelease()操作);
signal()则是把等待队列里的firstWaiter添加到同步队列里的尾部(enq(final Node node)操作)等待参与锁的竞争
await()
我们再看看ConditionObject,主要看await(),和signal()方法,类似于类似于object.wait()和object.notify();
我们先看await()方法
把当前线程封装一个Node赋值给ConditionObject的lastWaiter,如果是第一个也会赋值firstWaiter,然后挂起该线程
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);//释放当前的锁
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//判断当前节点是否已经在同步队列中,如果是则退出循环,如果不是就阻塞当前线程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
isOnSyncQueue(node))是我们后面这个线程去执行acquireQueued(node, savedState)重新竞争锁的关键,这个是收到其他线程的signal()之后放入的
我们看看signal()方法,主要看doSignal();
signal()
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)//把firstWaiter指向往后移
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && //把firstWaiter的node加到同步队列里去
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);//cas保证加入同步队列尾部
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
这个只是大致的浏览了下源码,并没有分析那么透彻,好多地方并没有去深究为什么要这样写这行代码有什么意义,仅仅是一边看看源码,一边总结的粗糙的理解,最后找个图看看这两个队列的操作
await()方法
signal()方法
关于AQS模板方法里的共享式获取acquireShared()和共享式释放锁releaseShared()在ReentrantLock的Sync里并没有重写tryAcquireShared(),共享锁指的是多个线程可以去拿这把锁,如果没有控制拿锁线程的数量其实可以理解为没有锁
共享锁在ReentrantReadWriteLock里的读锁里有体现,拿锁线程数量是static final int MAX_COUNT = (1 << 16) – 1;
总结:公平锁非公平锁就是在新的线程进来去拿锁的时候,是否去检测同步队列有没有排队的一步操作,去检测了然后去排队就是公平,不检测直接尝试拿锁就是非公平锁