前提回顾
AQS三部曲之下篇,本篇文章主要面向与对于AQS各个组件的实现的方式和原理。
源码分析
承接上一篇的结尾,进行分析相关的AQS的源码。
Node节点
首先我们来看看Node节点的实现到底是怎么回事,源码如下:
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;
/**
- 节点再等待队列中,节点的线程将会处于Condition等待状态,只有其他线程调用了
- Condition的signal()后,该节点由等待队列进入到同步队列,尝试获取同步状态。
*/
static final int CONDITION = -2;
/**
- 表示下一次共享式同步状态获取将会无条件传播下去
*/
static final int PROPAGATE = -3;
/**
- 后一个节点的等待状态:
- SIGNAL:表示这个节点的后继节点被阻塞,到时需要通知它。
- CANCELLED:由于中断和超时,这个节点处于被删除的状态。处于被删除状态的节
点不会转 - 换成其他状态,这个节点将被踢出同步队列,被GC回收。
- CONDITION: 表示这个节点再条件队列中,因为等待某个条件而被阻塞。
- PROPAGATE: 使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取
可以无条件传播. - 0: 新节点会处于这种状态。
*/
volatile int waitStatus;
/**
- 队列中,节点的前一个节点
*/
volatile Node prev;
/**
-
节点的后继节点.
*/volatile Node next;
/**
-
节点所拥有的线程.
*/volatileThread thread;
/**
- 条件队列中,节点的下一个等待节点.
*/
Node nextWaiter;
/**
-
判断节点时候是共享模式.
*/final boolean isShared(){
return nextWaiter == SHARED;
}
/**
-
获取前一个节点
*/
final Node predecessor()throwsNullPointerException{
Node p = prev;
if(p ==null)
throw new NullPointerException();
else
return p;
}
// Used to establish initial head or SHARED marker
Node() {
}// Used by addWaiter
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}// Used by Condition
Node(Thread thread,int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
//这个方法是自旋获取锁/获取不到锁线程挂起等待唤醒
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);
// help GC
p.next = null;
failed = false;
return interrupted;
}
//分析这个if语句 第一个条件把node的前驱节点的waitStatus置为-1 也就是signal状态
//如果这一步成功了 那么会进行parkAndCheckInterrupt方法的判断
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* Convenience method to park and then check if interrupted
* @return {@code true} if interrupted
* //可以看出这个方法就是让线程挂起,然后线程唤醒之后会返回线程的中断状态 和
前面的if语句结合
shouldParkAfterFailedAcquire(p, node)
parkAndCheckInterrupt() 都返回true会把interrupted置为true 然后还是会抢锁
* //返回最外层看看
*/
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
//lock方法的外层
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
// 可以看到 tryAcquire 和 acquireQueued 也就是抢锁失败 然后线程挂起, 唤醒后如果
// 断了还是会抢锁,最后 会调用interrupt()方法
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
// await方法的外层
if( acquireQueued(node, savedState) && interruptMode !=THROW_IE)
interruptMode =REINTERRUPT;
// clean up if cancelled
if(node.nextWaiter !=null )
unlinkCancelledWaiters();
if(interruptMode !=0)
reportInterruptAfterWait(interruptMode);
可以看到 第一个if语句,如果中断而且不是signal之前中断
interruptMode =REINTERRUPT;
最后一个if语句会处理中断,抛出中断异常或者调用interrupt方法
到这里可以看到即使中断了 还是会继续抢锁,别急往下看 看另外一个lock方法
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//有了之前的介绍 这个方法几步多说了
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
/**
* Acquires in exclusive interruptible mode.
* @param arg the acquire argument
* 可以看到 shouldParkAfterFailedAcquire和parkAndCheckInterrupt都是true的话 会直接抛出InterruptedException异常
* 然后会在finally里cancelAcquire方法
* 而且外层也不会处理 直接抛出 那么
*/
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
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; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
* Cancels an ongoing attempt to acquire.
-
@param node the node
- 这个就是如何取消队列本尊了
*/
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist if (node == null) return; node.thread = null; // Skip cancelled predecessors //跳过取消的节点 一直往前找waitStatus<=0的节点 Node pred = node.prev; while (pred.waitStatus > 0) node.prev = pred = pred.prev; // predNext is the apparent node to unsplice. CASes below will // fail if not, in which case, we lost race vs another cancel // or signal, so no further action is necessary. Node predNext = pred.next; // Can use unconditional write instead of CAS here. // After this atomic step, other Nodes can skip past us. // Before, we are free of interference from other threads. node.waitStatus = Node.CANCELLED; // If we are the tail, remove ourselves. //如果node是末尾节点 而且设置node的前驱节点为等待队列的末尾节点成功 那就设置 node的前驱节点的下一个节点为null if (node == tail && compareAndSetTail(node, pred)) { compareAndSetNext(pred, predNext, null); } else { // If successor needs signal, try to set pred's next-link // so it will get one. Otherwise wake it up to propagate. //如果node的pred不是head 而且perd的waitStatus能设置为signal 那么就把pred.next = node.next也就是 把node去掉 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; // help GC }
}
下面来介绍一下线程中断
线程中断不是类似linux里面的kil -9pid ,也就是说线程中断了,就立即停止了.中断代表的是线程的状态(还有其他含义),
初始值是false
Thread实例的方法
//获取 线程的中断状态 不清楚
public boolean isInterrupted() {
return isInterrupted(false);
}
/**
* Tests if some Thread has been interrupted. The interrupted state
* is reset or not based on the value of ClearInterrupted that is
* passed.
* 看解释 如果参数为true就会清楚线程的中断状态 也就是重置为false
*/
private native boolean isInterrupted(boolean ClearInterrupted);
用于设置一个线程的中断状态为 true
public void interrupt() {
}
// Thread 中的静态方法,检测调用这个方法的线程是否已经中断
// 注意:这个方法返回中断状态的同时,会将此线程的中断状态重置为 false。
// 所以,如果我们连续调用两次这个方法的话,第二次的返回值肯定就是 false 了
public static boolean interrupted() {
}
对于一些方法 线程中断了
可以感知到 例如LockSupport.park(this);
线程挂起后 如果调用xxthread.interrupt()方法,线程会被唤醒 然后响不响应中断是自己的事
例如上面的两种lock方法 一种响应抛出InterruptedException异常, 另外一种只是位置线程的中断状态
列举一些能感知线程被中断的方法
来自 Object类的 wait()、wait(long)、 wait(long, int),来自 Thread类的 join()、join(long)、join(long, int)、sleep(long)、sleep(long, int)
这几个方法的相同之处是,方法上都有:throws InterruptedException
2.如果线程阻塞在这些方法上(我们知道,这些方法会让当前线程阻塞),这个时候如果其他线程对这个线程进行了中断,那么这个线程会从这些方法中立即返回,
抛出 InterruptedException
异常,同时重置中断状态为 false。
实现了 InterruptibleChannel
接口的类中的一些 I/O 阻塞操作,如 DatagramChannel中的 connect方法和 receive方法等
如果线程阻塞在这里,中断线程会导致这些方法抛出 ClosedByInterruptException并重置中断状态。
3.Selector 中的select 方法, 这个有机会我们在讲 NIO的时候说一旦中断,方法立即返回
对于以上 3种情况是最特殊的,因为他们能自动感知到中断(这里说自动,当然也是基于底层实现),并且在做出相应的操作后都会重置中断状态为 false。
InterruptedException 概述
它是一个特殊的异常,
不是说 JVM
对其有特殊的处理,而是它的使用场景比较特殊。通常,我们可以看到,
像 Object中的 wait() 方法,ReentrantLock 中的lockInterruptibly() 方法,
Thread 中的sleep() 方法等等,这些方法都带有 throws InterruptedException,我们通常称这些方法为阻塞方法(
blocking method)。
阻塞方法一个很明显的特征是,它们需要花费比较长的时间(不是绝对的,只是说明时间不可控),还有它们的方法结束返回往往依赖于外部条件,
如 wait
方法依赖于其他线程的 notify,
lock 方法依赖于其他线程的
unlock等等。
当我们看到方法上带有 throws
InterruptedException 时,我们就要知道,这个方法应该是阻塞方法,我们如果希望它能早点返回的话,我们往往可以通过中断来实现。
除了几个特殊类(
如 Object,Thread等)外,感知中断并提前返回是通过轮询中断状态来实现的。我们自己需要写可中断的方法的时候,就是通过在合适的时机(通常在循环的开始处)去判断线程的中断状态,
然后做相应的操作(通常是方法直接返回或者抛出异常)。当然,我们也要看到,如果我们一次循环花的时间比较长的话,那么就需要比较长的时间才能注意到线程中断了。
怎么处理中断 可以参考AQS怎么做的
lock方法 这个不方法不响应中断但是也没有吧中断信息给丢掉
在最外层 还是把线程的中断状态给设置为true
自己写代码时可以自行检测
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
lockInterruptibly 方法
上面介绍过 这里就不详细介绍了
该方法 检测到中断
直接抛出中断异常 而且不做处理
向外层抛出
在java的并发包中 对于中断的响应都是分为两类
响应不响应,不响应也不会把中断信息丢掉,记录状态