大家推荐个靠谱的公众号程序员探索之路,公众号内点击网赚获取彩蛋,大家一起加油,这个公众号已经接入图灵
1.AQS独占锁的取消排队
//这个方法是 自旋获取锁/获取不到锁线程挂起等待唤醒
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语句 第一个条件把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;
if(node.nextWaiter !=null) // clean up if cancelled
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
}
}
2.下面来介绍一下线程中断
线程中断不是类似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异常, 另外一种只是位置线程的中断状态
列举一些能感知线程被中断的方法
1.
来自 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的并发包中 对于中断的响应都是分为两类
响应不响应,不响应也不会把中断信息丢掉,记录状态