AQS打开JUC的钥匙
相关概念
独占模式占有锁
独占模式释放锁
Condition.await线程等待part1
Condition.signal
Condition.await线程等待part2
Condition.signalAll()
共享模式占有锁
共享模式释放锁
tryLock(long timeout,TimeUnit unit)
相关概念
在JUC同步包下的类几乎都需要有同步,等待操作.这些操作都依赖于(AbstractQueuedSynchronizer)(AQS).例如ReentrantLock,ThreadPoolExecutor.AQS继承自AbstractOwnableSynchronizer(AOS).
AQS有两个内部类,Node,ConditionObject.在lock,await操作时需要两个队列,同步队列,等待队列.这两个队列都依赖于Node对象形成链表(用链表实现队列).ConditionObject则提供了await,signal的实现。
AQS同步队列分为独占模式(SHARED)和共享模式(SHARED)但是真正需要的是独占锁还是共享锁,AQS不做实现,由使用者也就是其子类去实现.与之相关的方法有5个。
tryAcquire获取独占锁,tryRelease释放独占锁,tryAcquireShared获取共享锁,tryReleaseShared释放共享锁,isHeldExclusively查看锁是否锁定状态
整个抽象类AQS没有一个抽象方法,只有这5个直接抛出异常的方法,这也正是设计的巧妙之处,因为AQS本身支持共享锁和独占锁两个功能,如果设置成抽象方法那子类一定要完整的重写5个方法才行。
这种设计方法如果是独占锁只需要重写tryAcquire,tryRelease,isHeldExclusively如果是共享锁只需要重写tryAcquireShared,tryReleaseShared,isHeldExclusively
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中关键的属性有两个,其一是private volatile int state;它标记了锁是否可用,独占锁或者共享锁都是对这个属性的操作,它被volatile修饰保证了可见性.另一个属性是Node中的volatile int waitStatus;它有0,1,-1,-2,-3五种状态.Node中封装了线程,围绕着lock,unlock,await,signal几个关键方法。
独占模式占有锁
作为切入点,我们用ThreadPoolExecutor内部类Worker它继承自AQS,内部有lock,unlock的操作,并且它是独占锁的实现。
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
protected boolean isHeldExclusively() {
return getState() != 0;
}
public void lock() { acquire(1); }
public void unlock() { release(1); }
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
lock()和unlock()传递的参数都是1.tryAcquire()用CAS操作将state0替换为1,意味着占有锁成功,然后将当前线程设置到AOS中.如果CAS失败则表示占有锁失失败.tryRelease将AOS中的线程置null然后将state通过CAS的方式修改成0,表示释放锁。
AOS的代码很简单只封装了一个Thread表示占有锁的线程.
public abstract class AbstractOwnableSynchronizer implements java.io.Serializable {
protected AbstractOwnableSynchronizer() { }
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
}
lock方法调用的是acquire();
public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
要占用锁首先就要使用子类覆盖的tryAcquire()而Worker的关键实现是compareAndSetState(0, 1);它通过CAS操作将state从0修改为1,如果修改成功则表示占有锁成功.如果tryAcquire返回true就不会执行其他代码了。
这里使用了Unsafe类,这个类是比较强大CAS操作和阻塞线程操作都是调用的它的方法,而它本身的方法都是native,想要详细了解的同学可以参考我的另一篇博客https://www.cnblogs.com/zumengjie/p/14687484.html
private static final Unsafe unsafe = Unsafe.getUnsafe();
private volatile int state;
private static final long stateOffset;
static {
try {
stateOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
} catch (Exception ex) { throw new Error(ex); }
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
如果tryAcquire失败,先走addWaiter()入参是Node.EXCLUSIVE,它仅仅标记当前Node是独占模式.这里有必要把Node的源码贴出来,不复杂是一个标准的链表Node。注意新创建的Node属性waitStatus是0
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
如果走addWaiter()表示第一步占有锁的操作失败了,当前线程不能执行.首先创建一个Node封装当前线程,然后找到队列末尾的元素tail。如果tail存在那表示同步队列中有其他的Node,那身为新来的当然是最后一个也就是新的tail.注意compareAndSetTail()是可能失败的,在并发环境下,会存在多个Node同时加入队列,那么谁先CAS成功谁就是tail.
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private static final long tailOffset;
static {
try {
tailOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
} catch (Exception ex) { throw new Error(ex); }
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
如果tail不存在或CAStail失败进入enq().如果是tail不存在的情况则先创建一个哑节点当作head和tail,创建结束后自旋到else将封装有Thread的Node添加到队列末尾.如果是tail存在addWaiter()处添加到tail失败的则直接进入else.enq是一个自旋方法,无论是哪种情况带有Thread的Node都会被成功添加到tail.然后返回addWaiter()
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
private static final long headOffset;
static {
try {
headOffset = unsafe.objectFieldOffset(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
}
}
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
addWaiter()结束后继续走acquireQueued().当前线程已经被封装到Node添加到了队列中,进入自旋后查看自己的prev是否是head也就是查看自己是否是老二,如果自己是老二那么还是可以在tryAcquire()一次尝试上位,这个前提必须是当前占有锁的线程释放了锁也就是state是0,否则老二是无法上位的.如果老大释放了锁老二上位了,则将自己修改为head,prev.next=null此操作在于gc时可以把老大给清理掉.如果自己都不是老二或者老大没退位,那执行shouldParkAfterFailedAcquire()
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;
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在这里牵扯waitStatus的两个状态-1(SIGNAL)和1(CANCELLED)如果prev.status是-1的则直接结束,如果prev.status>0只有1这种情况它表示前置节点是取消状态不可用,通过循环的方式从prev处向前寻找可用可用节点如果找到了,则将node于可用的prev连接.prev和next操作就是连接两个Node.然后再次自旋判断prev的状态是否是-1如果是返回,如果不是设置其为-1(SIGNAL).在这个步骤我们确定了SIGNAL表示Node正在同步队列中.在这里要说下,并不是所有的Node节点的状态都会变成-1,如果一个节点添加到tail后,他没有子节点了,那么
最终竞争锁成功后它的状态还是0.也就是说如果一个节点的状态是0表示它没有后继节点.
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 {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private static final long waitStatusOffset;
static {
try {
waitStatusOffset = unsafe.objectFieldOffset(Node.class.getDeclaredField("waitStatus"));
} catch (Exception ex) { throw new Error(ex); }
}
private static final boolean compareAndSetWaitStatus(Node node,int expect,int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,expect, update);
}
从shouldParkAfterFailedAcquire()结束则走到获取锁的最后一个环节,将线程阻塞。在阻塞结束后线程会继续在acquireQueued方法内尝试占有锁,在独占模式下,必然成功,因为独占模式下释放锁的操作仅仅会唤醒一个Thread,在释放锁步骤会看到.
阻塞结束后返回线程是否中断的状态.这里使用的是interrupted()如果返回true则表示线程中断了,但这个方法会将中断信号修改为false所以当线程阻塞结束后,如果返回的是true则会在acquireQueued()内设置一个中断标记,如果该线程成功占有了锁,则acquire()会调用selfInterrupt()给线程重写设置中断信号.中断信号本身对于Thread并没有影响,它于stop()不同,中断信号不会做出任何操作,如果需要则应该在线程内部主动检测这个信号.
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
总结下占有锁的步骤:
1 acquire()中tryAcquire()返回true直接占有锁成功.
2 tryAcquire()失败走addWaiter()它将线程添加到队列末尾.
3 acquireQueued()自旋操作,在parkAndCheckInterrupt()阻塞线程前,如果自己是第二个线程并且锁释放了,那么该线程会将自己设置为head并持有锁.
4 shouldParkAfterFailedAcquire()它将Node.prev设置为SIGNAL然后执行parkAndCheckInterrupt()阻塞自己.
5 acquireQueued()有finally这里有一个cancelAcquire()操作,它的条件是failed没有变成false,也就是在不断的自旋中没有获取锁并且抛出了异常在acquireQueued()方法调用链中只有node.predecessor()方法可能会抛出空指针.但是问题在于除了head节点没有prev任何的节点都有prev,head节点又不需要竞争锁.这acquireQueued()里感觉这个finally没有什么用但是doAcquireSharedInterruptibly()方法确有用处,再下边会说到.
独占模式释放锁
释放锁的入口是release(),tayRelease()把状态设置成0,判断是否有head节点,如果当前AQS中只有一个线程并且它也占有了锁,此时是没有head的.如果有并且head节点的status不是0就进入unparkSuccessor(),为什么每一个新加入的Node都在shouldParkAfterFailedAcquire()处将prev.status设置成了-1,如果head.waitStatus是0那只能表示它没有后继节点所以也不需要唤醒!就是在这里体现的.除了首次的哑节点没有封装线程其余的Node都封装的有线程,head永远代表当前正在执行的线程。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final void setState(int newState) {
state = newState;
}
将head.status设置成0,初始化是0,进入队列后是-1,释放锁又恢复成0,然后拿到head.next判断老二是不是null,老二的状态是否大于0,大于0的状态只有1(CANCELLED)两种情况都表示老二不可用,进入循环从tail开始向前寻找,循环到head找到距离head最近的一个可用Node,这个就是下一个要被唤醒的线程,LockSupport.unpark()唤醒阻塞的线程。这就是释放锁的过程,总结说就是先将status设置成0然后从head往后寻找一个状态不是1的Node,这里会留下疑问,难道除了1其他的都可以吗?如果是-1是可以的,那么会不会有-2,-3的情况呢?最后取消最近一个可用Node的阻塞状态。
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
Condition.await线程等待part1
说到lock()和unlock()就不得不提到与之相关的await()和sinal()在ReentrantLock通过newCondition()获取Condition而这个Condition正是AQS中的ConditionObject().
public class ReentrantLock implements Lock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
final ConditionObject newCondition() {
return new ConditionObject();
}
}
}
从await()开始看看是如何使线程等待的.ConditionObject也是通过Node队列的方式保持被await()的线程.firstWaiter,lastWaiter构成了头节点和尾节点.进入await()首先可以看到的是这个方法是响应中断的,也就是如果该线程已经是中断状态,在调用await()会抛出异常.
public class ConditionObject implements Condition, java.io.Serializable {
private transient Node firstWaiter;
private transient Node lastWaiter;
private static final int REINTERRUPT = 1;
private static final int THROW_IE = -1;
}
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)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
await()首先要做的是将线程添加到等待队列中,addConditionWaiter()首先它拿到尾部节点,然后创建一个新的Node,此时Node的状态为-2(CONDITION).根据尾部节点分为两种简单的情况1没有lastWaiter,说明当前等待队列为空,将node设置成firstWaiter.情况2有lastWaiter这种情况只需将node添加到lastWaiter后边即可,需要注意的是在等待队列中不是使用的next而是nextWaiter而在同步队列中nextWaiter是锁模式的标记,再次注意这里没有设置node.prev意味着等待队列是一个单项链表.无论是这两种情况的哪一种都可以将node设置成lastWaiter.情况3有lastWaiter但是它的状态已经不是CONDITION了,在这里可以确定在等待队列中的Node.status必须是CONDITION.执行unlinkCancelledWaiters()
private Node addConditionWaiter() {
Node t = lastWaiter;
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
获取firstWaiter从first开始循环每次都查询next.如果当前节点是CONDITION状态则将当前节点记录到trail可以理解为临时节点.然后指针向下推进。如果当前节点状态不是CONDITION则表示这个节点需要清除,把它的nextWaiter取消关联,判断是否有trail前边说了trail是
临时节点这个节点的状态肯定是CONDITION的.如果没有这个节点表示当前队列中没有一个可用节点则next成为firstWaiter它可能也是不可用的,但没关系循环会继续.如果trail有表示队列中有可用的节点,则将next与trail连接.如果此时next是null表示已经到了队列末尾,那么上一个可用的trail就是lastWaiter.最后next是null停止循环.这个方法的就是通过循环的方式将等待队列中状态不是CONDITION的Node剔除队列。
接下来是fullyRelease()它的核心是release()这个方法在释放锁时介绍过,需要注意的是只有占有锁的线程才可以调用await()而调用await()后会释放锁的操作也就是在这里实现的.在释放锁时还留了个问题waitStatus会不会是-2,-3现在看了起码不会是-2因为-2只出现在等待队列中.注意这里的返回值,就算是独占锁的情况,saveState也不是一定是1因为可能一个线程重入多次,最终将这个返回值保留也是为了等该线程从等待处唤醒时再次持有相同数量的锁.如果释放锁失败了则抛出异常然后走finally这finally中将Node设置为1(CANCELLED)这表示在等待队列中status为1依然是一种不正常的状态.
final int fullyRelease(Node node) {//AQS
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean release(int arg) {//AQS
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {//AQS
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
、
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
把线程添加到等待队列然后从同步队列中踢出,接下来就该阻塞线程了,这里需要再次注意Node.prev,Node.next只存在于同步队列,Node.nextWaiter存在于等待队列.所以isOnSyncQueue()中有多个判断Node.CONDITION肯定在等待队列,Node.prev!=null肯定不在同步队列中这两种情况都返回false.如果Node.next有值则不保险了,说明它可能在同步队列.最后findNodeFromTail()再次从同步队列的尾部开始查询当前Node是否存在于同步队列.顺着这个流程下来的Node必然不会再同步队列,因为这个Node是在addConditionWaiter()中创建的新的Node.
k//AQS
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null)
return true;
return findNodeFromTail(node);
}
//AQS
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
最后LockSupport.park(this);阻塞该线程.剩下的部分就是线程被唤醒后的操作,我们先去看如何唤醒等待的线程.
Condition.signal
唤醒锁的第一步是isHeldExclusively()它把AOS里保存的占有锁的线程与当前线程对比,如果是false则抛出异常,必须保证调用signal()的必须是当前占有锁的线程.
然后doSignal()唤醒等待队列中第一个Node如果等待队列中没有Node则什么都不做.
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
//ReentrantLock
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
doSignal()先把first.nextWaiter置为null被唤醒的Node不在是first节点也就不需要nextWaiter.transferForSignal()先把first.status从-2(CONDITION)设置为
0(Node初始状态)CAS操作返回false返回false的原因是first节点已经不是CONDITION了或许是CANCELLED然后transferForSignal()返回false.然后再次回到doSignal()此时就可以看出do{}while()其实就是找到有效的first节点.
doSignal()找到一个可用的first交给transferForSignal()然后在交给enq().enq()方法在获取同步锁失败后使用过,它的作用是将Node添加到同步队列的tail节点.然后返回原tail.
如果到这里就结束了过程也就很明晰了,首先拿到等待队列的first节点然后将其从first位置上拽下来然后将这个Node添加到同步队列的尾节点.此时这个线程依然是阻塞状态需要等待同步队列中的前驱节点一个个执行结束然后轮到这个节点被唤醒.被唤醒后
应当在await()方法内.但在这里两种情况会提前唤醒该节点,第一这个同步队列中的原tail.status<=0.根据前驱是SIGNAL的要求需要将它设置成SIGNAL如果设置失败了会提前执行await(),设置失败的原有可能是前驱已经不是0或-1了.可能是1,这种情况其实跟ws>0是一样的.
就是当前tail的上一个节点是非正常状态的.此时要提前唤醒当前节点.提前唤醒有什么用?我们继续回到await()方法看看唤醒后的操作.
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) && (first = firstWaiter) != null);
}
//AQS
final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
//AQS
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
Condition.await线程等待part2
从signal出来后线程可能因为前驱节点的取消而提前唤醒也可能在同步队列中排队被唤醒.被唤醒后首先执行的是checkInterruptWhileWaiting()
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)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
首先说明一个知识点.LockSupport.park()是响应中断的.也就是能进入checkInterruptWhileWaiting()并不一定是有线程执行了signal().如果返回0表示当前线程没有被中断,如果有被中断就要执行transferAfterCancelledWait()去判断到底是什么时间被中断的.
如果CAS成功表示Node.status是CONDITION,只有Node在等待队列中也就是还没有被signal()因为一旦执行了signal()就会把它添加到同步队列status要么是0要么是-1.所以返回true表示是在await()被中断的.如果不是CONDITION就走while循环isOnSyncQueue()判断
Node是否在同步队列,按道理说如果没有在等待队列那肯定是在同步队列但有一种情况就是signal()被调用了但transferForSignal()内部还没走到enq().所以yield()停一下直到Node被添加到同步队列中返回false.checkInterruptWhileWaiting()就是要判断当前线程
是否是因为被中断而唤醒的.如果不是返回0,如果是则判断是否是在await()时被唤醒的如果是返回-1如果不是返回1.
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
enq(node);
return true;
}
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
如果是因为中断唤醒的则直接跳出while()如果不是则还需要再次判断isOnSyncQueue()这个方法是查询是否在同步队列,如果不是因为中断唤醒那有两种情况也就是在signal()的结束时的两种情况.1 正常在同步队列中被唤醒 2 从等待队列转移到同步队列后发现
prev是取消状态.但无论哪一种情况Node肯定是在同步队列中的.此时再次执行acquireQueued()尝试抢占锁.如果抢占成功则将Node设置为head.否则parkAndCheckInterrupt()再次被阻塞.无论是否再次被阻塞.acquireQueued()被唤醒后总要返回一个中断标记.如果没有
被中断则继续往下走,如果被中断了则再去看上一次中断的标记是否是-1,如果不是-1则将中断标记设置为1这里费劲倒腾还是为了保证如果是在await()中断那么interruptMode一定要是-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;
failed = false;
return interrupted;
}
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)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
接下来判断Node.nextWaiter是否存在.只有在等待队列才会有nextWaiter.如果有就要执行unlinkCancelledWaiters().这个方法是用来清理等待队列中Node不是CONDITION的.transferAfterCancelledWait()说到如果因为中断被唤醒的则将CONDITION设置成了0然后返回
true才判断中断是发生在await()里边.所以unlinkCancelledWaiters()是需要清理掉这个在await()里被中断的Node.
private void unlinkCancelledWaiters() { Node t = firstWaiter; Node trail = null; while (t != null) { Node next = t.nextWaiter; if (t.waitStatus != Node.CONDITION) { t.nextWaiter = null; if (trail == null) firstWaiter = next; else trail.nextWaiter = next; if (next == null) lastWaiter = trail; } else trail = t; t = next; } }
最后才是说明为什么要判断中断到底在什么时间发生的如果interruptMode!=0要么是1要么是-1.都是中断了.如果是-1则需要抛出异常.这也就是为什么线程在await()时如果中断了会抛出异常的原因.如果是1则表示不是在await()发生的中断,只需要重置中断信号.
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
最后在说下留在signal()最后的疑问,为什么前驱节点被取消了要立刻唤醒当前节点呢?现在可以看到一个节点被唤醒后的操作实际上最主要的还是acquireQueued()让线程去抢占锁,如果抢占失败还需要执行shouldParkAfterFailedAcquire()这个方法就是让前驱的
状态是SIGNAL获取前驱不可用则需要一直向前找到一个可用的前驱.最后将线程再次LockSupport.park().所以如果已经直到前驱不可用就应该立刻找到一个可用的前驱shouldParkAfterFailedAcquire()这也是提升了效率啊!不得不说这些前辈的编码功夫真的是厉害!
Condition.signalAll()
看完了await()和signal()在看个简单的.signalAll().signalAll()和signal()的区别就是会唤醒等待队列中全部的线程.也就是把他们全部转移到同步队列中.关键方法就是doSignalAll()它用循环的方式将first传递给transferForSignal()这个方法则是将Node转移到
同步队列中,不算复杂.
public final void signalAll() { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); Node first = firstWaiter; if (first != null) doSignalAll(first); } private void doSignalAll(Node first) { lastWaiter = firstWaiter = null; do { Node next = first.nextWaiter; first.nextWaiter = null; transferForSignal(first); first = next; } while (first != null); } final boolean transferForSignal(Node node) { if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) return false; Node p = enq(node); int ws = p.waitStatus; if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)) LockSupport.unpark(node.thread); return true; }
共享模式占有锁
AQS除了提供独占锁之外还提供了共享锁,所谓共享锁就是允许多个线程同时占有锁.Semaphore类就是共享锁的实现.我们先从构建Semaphore看.Semaphore提供了一个带参的构造函数permits就是允许共享的数量.Semaphore和ReentrantLock都分为公平锁和非公平锁,默认
都是非公平锁Semaphore构造了NonfairSync将permits传递给了Sync而Sync就是AQS的子类.它将permits设置到了AQS的state成员上.这与独占锁不同独占锁根据0或1判断锁是否被占用,而共享锁则是给出了锁的数量.
public class Semaphore implements java.io.Serializable {
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
abstract static class Sync extends AbstractQueuedSynchronizer {
Sync(int permits) {
setState(permits);
}
}
static final class NonfairSync extends Sync {
NonfairSync(int permits) {
super(permits);
}
}
}
protected final void setState(int newState) {//AQS
state = newState;
}
Semaphore获取锁的方法是acquire()方法内部调用了Sync的acquireSharedInterruptibly但实际上它是AQS的方法.
public class Semaphore implements java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
}
static final class NonfairSync extends Sync {
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
}
acquireSharedInterruptibly()响应中断信号然后就要调用子类的tryAcquireShared()尝试获取锁.NonfairSync实现了这个方法但是内部还是使用它的父类Sync的nonfairTryAcquireShared().进入方法后就是自旋操作,获取当前state的值,然后减去(1)acquires
得出的差如果小于0表示已经没有多余的锁了,如果等于或者大于0通过CAS设置state这里其实是将state-1.有一种情况如果当前两个线程获取state都是1都减去1后都是0然后都去CAS设置state但是只有一个会成功,失败的那个只能再次自旋,此时state-1就是小于0了.
占有锁失败.返回到acquireSharedInterruptiby()占有锁失败后进入doAcquireSharedInterruptiby()
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
doAcquireSharedInterruptiby()和独占锁的acquire()流程十分类似,第一步都是将线程添加到同步队列中,不过共享锁创建的Node类型是SHARED.这里为了阅读方便也将addWaiter()贴出来.如果同步队列中tail存在则将将newNode添加到同步队列末尾.如果添加失败
或者队列中没有元素则走enq()在enq()中要么先创建一个哑节点,要么将newNode自旋的方式添加到tail.addWaiter()执行结束后没有像独占锁那样执行acquireQueue()而是直接把代码实现写了出来,但和独占锁的acquireQueue()十分类似.进入自旋后首先拿到前驱节点
如果前驱节点是head表示自己可以尝试竞争下锁.如果竞争失败了还是走shouldParkAfterFaileAcquire()把前驱节点设置成-1(SIGNAL)然后将线程阻塞.
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
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 {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
这个流程只有两个地方和独占锁不同一个是它响应中断,如果中断了会抛出异常,此时就会执行cancelAcquire()另一方面如果竞争锁成功了并不是简单的执行了setHead()而是setHeadAndPropagate()先说cancelAcquire().setHeadAndPropagate()在释放锁时说.cancelAcquire()进入方法后首先判断如果Node是null返回.如果Node有则将Thread置为null.找到它的prev.while循环则是防止prev也是取消状态.
如果也是取消状态则在找prev一直找到一个可用的前驱节点.此时的pred要么就是Node的prev要么跟它自己隔了好几个取消状态的Node.这也意味着predNext可能就是node也可能是其他被取消的前驱node.有两种情况1是Node是为节点,那就好说了,直接把pred设置成
tail.如果成功则将pred.next设置成null因为tail节点没有后继节点.如果Node不是tail或者它设置tail失败,总之现在不是tail了.这就是情况2.node节点在中间.此时需要看pred是不是头节点,如果不是头节点并且状态是SIGNAL或可以将pred设置成SIGNAL成功
此时已经保证了pred是正常节点,就需要将node.next设置给pred.next前提是next存在.
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
node.waitStatus = Node.CANCELLED;
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node;
}
}
如果pred是头节点.执行upparkSuccessor()这个方法把Node的状态设置成0并获取它的next如果next是cancel也要从tail开始找到一个距离Node最近的一个正常Node然后唤醒它.综合上下文如果被唤醒的线程并且被中断的线程它的prev是还是head那只有一种情况这个
Node.prev是哑节点.这个Node如果没有被中断那么它接下来应该去占有锁,但它中断了所以只能唤醒它的next节点.
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
共享模式释放锁
首先先看一个demo.Semaphore设置的信号量都是3也就是允许3个线程占有锁.线程t占有锁时直接拿了3个,是的它可以选择拿几个.释放时也释放了3个.而t2线程则需要占有4个所以一直自旋拿不到锁.
这个demo与上一个不同的是t线程拿了3个锁但是释放了4个锁.所以t2可以成功占有锁.从这两个demo可以看出对于共享锁.能不能获取锁完全取决于state够不够分配给即将要占有的线程.释放锁则更毫无顾忌,释放多了也无所谓.
释放锁的入口方法是release()方法只判断了permits不能小于0.然后调用了Sync的releaseShared()但这个方法是AQS的.方法进入就又调用了子类的tryReleaseShared().尝试释放锁.tryReleaseShared()又是一个自旋,首先它如果state+releases还小于state则直接抛出异常
释放前是state要释放的锁数量是releases两个相加还不如释放前肯定要异常.然后就CAS去修改state的值但可能会失败,这就是共享锁的特性,因为共享锁同时会有多个线程占有锁,释放锁也是一个并发操作所以释放失败说明有别的线程提前释放了.没关系自旋一下重新来就可以.
重点在于doReleaseShared()它也是一个自旋操作首先拿到head节点,如何head节点然后判断head节点存在并且不是tail不是tail表示还有后继节点,那么根据独占锁的经验它肯定也是要唤醒后继节点的.然后拿到head.status如果它是SIGNAL后继节点确实在同步阻塞中然后CAS尝试修改SIGNAL为0但是如果修改失败了会重新自旋,为什么会失败?设想一种情况现在有甲,乙两个线程同时doReleaseShared()此时同步队列中有A,B两个线程A线程是head节点.甲乙两个线程都拿到了B线程然后判断它是SIGNAL都要CAS但是只有一个可以设置成功.另一个
就是失败咯.例如甲成功修改了head的state然后进入了unparkSuccessor()则会唤醒head.next然后线程被唤醒后是从doAcquireShared()处的自旋继续执行尝试占有锁,注意了此时head.status还是0乙线程再次自旋准备CAS把head.status修改成-3(PROPAGATE)status的5个状态终于集齐了.但是就在乙要CAS时.被唤醒的A线程去占有锁但是失败了,在共享锁中占有锁失败很正常如果甲只释放了一个信号量,但是B线程需要100个那肯定失败咯.B线程失败后就走了shouldParkAfterFailedAcquire()又把head设置为了SIGNAL你说狗不狗.所以乙线程只能再次自旋,这次它判断head是SIGNAL了然后设置成了0又释放了B线程,这时B线程到底能不能占有锁还是要看乙释放了多少个锁.这里再总结下PROPAGATE这个状态,它仅仅表示head节点被释放了next但是next这个线程还未占有锁也没用占有锁失败的这一个中间状态,因为如果next占有锁成功它自己就是head了,如果占有锁失败那它又把head设置为SIGNAL.
最后想不走continue只能是unparkSuccessor()结束.此时被释放的线程还正在抢占锁head就还是原有的head这次h==head跳出循环.那么有没有可能h!=head呢?有如果被唤醒的线程抢占了锁那它就是新的head此时循环还是不能结束.且不能忘了进入这个循环中一定是有
释放锁的操作,如果自己释放了锁但是发现head变化了只能是再次对新的head做操作.不能让自己释放了信号量但是没有唤醒线程,哪怕唤醒了已经它去抢占失败也不是自己的问题.
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
if (h == head)
break;
}
}
public class Semaphore implements java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
}
public void release(int permits) {
if (permits < 0) throw new IllegalArgumentException();
sync.releaseShared(permits);
}
}
最后再来把抢占锁成功这个坑填了.如果当前线程抢占锁成功了,那r要么是0要么大于0因为小于0说明信号量不够.就是抢占锁失败嘛.抢占成功后走setHeadAndPropagate()这个方法很怪很怪.
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null;
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node)
if (propagate > 0 || h == null || h.waitStatus < 0 || (h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
进入setHeadAndPropagate()后首先拿到head然后将自己设置成head这没毛病.变量h是前head.然后就是这if里有4个||5种情况然后再配合里边if的2种情况.总共有10中情况都会调用doReleaseShared()一个字绝.
首先确定doReleaseShared()这个方法就是去唤醒head之后的节点.再上边已经很详细的聊过了,唤醒以后线程去抢占锁但成功与否无关,所以doReleaseShared()是有很多情况瞎跑的,但瞎跑不会影响AQS的流程.为什么我要这么说呢.因为这10中情况我解释不完....
propagate>0和s==null 表示信号量还有剩余允许当前线程节点的后继节点尝试唤醒锁,如果表示当前线程没有后继节点但作为共享锁现在没有next不代表永远没有只要你信号量就去尝试唤醒总是没错的.万一这一会的工夫就有next了呢.
propagate>0和s!=null s.isShared锁标识是共享锁. 表示信号量还有剩余允许当前线程节点的后继节点尝试唤醒锁,此时去doReleaseShared()是最好的时机,有信号量有后继节点.
oldHead.waitStauts和newHead.waitStatus<0表示有后继节点,此时就算没有信号量再去跑一下doReleaseShared()也没问题,毕竟现在没不代表等下也没有.
h==null和(h=head)==null这两种情况只能存在于GC.OldHead和newHead都已经执行结束了再新head上任前两个前辈就被GC了.这种情况按道理是不用doReleaseShared()这确实不知道为何会这样.
这个方法到此结束.有用的就是propagate>0这种情况是完全可以去继续唤醒next的.
tryLock(long timeout,TimeUnit unit)
tryLock()可以设置获取锁的超时时间,允许在指定时间内获取锁就可以.我们从ReentrantLock进入.先将timeout转换成纳秒.然后调用AQS中的tryAcquireNanos().tryAcquireNanos()先尝试获取锁,如果获取锁成功则返回true,如果获取失败则调用doAcquireNanos()
//ReentrantLock public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } //AQS public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); } //ReentrantLock protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } //ReentrantLock 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) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } //AQS static final long spinForTimeoutThreshold = 1000L; //AQS private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (nanosTimeout <= 0L) return false; final long deadline = System.nanoTime() + nanosTimeout; final Node node = addWaiter(Node.EXCLUSIVE); boolean failed = true; try { for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; failed = false; return true; } nanosTimeout = deadline - System.nanoTime(); if (nanosTimeout <= 0L) return false; if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) LockSupport.parkNanos(this, nanosTimeout); if (Thread.interrupted()) throw new InterruptedException(); } } finally { if (failed) cancelAcquire(node); } }
deadline是当前时间与timeout的和.接下来的操作与普通的acquire()一样都是先创建Node然后将其添加到同步队列尾部.然后自旋尝试获取占有锁,如果占有成功返回true.则计算nanosTimeout这个值就是算是否超时了,如果nanosTimeout<=0表示已经超时则返回占有
锁失败.否则还是执行shouldParkAfterFailedAcquire()接下来再次判断nanosTimeout是否大于1000L如果大于了说明距离timeout还有很长时间则可以先把线程阻塞此时调用的就是LockSupport.parkNanos().