线程之Timeout
Timeout ,我之前的理解,如果报了TimeoutException,不就意味着程序运行的规定时间到了,程序要结束了。但是再琢磨一下,怎么做呀?怎么定一个时间,等时间结束,程序就结束。
看过很多程序结束的方式,其中一种是获得程序运行的线程,然后,调用线程中断命令,当程序中检测到中断指令时,抛出异常,终止程序。等一下… 检测到中断指令,怎么才能时刻检测到中断指令,写个死循环?如下面代码
public void run(){
try {
for(;;){
if(Thread.interrupted()){
throw new InterruptedException();
}
runAnotherCode();
}
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println("Stop"+e);
}
}
这明显有大病,runAnotherCode() 只要跑一次,跑的时候,怎么随时检测中断,以致于将runAnotherCode() 程序停掉。
好吧,冥思苦想,不得解,看看书吧,好家伙,在《 Java 并发编程实战》里面有介绍——任务的取消。其中,直接给出,Java 没有提供任何机制来安全地终止线程,但是它提供了中断,这是一种协作机制,能够使一个线程终止另一个线程的当前工作。在回想,之前的线程或线程池的退出,都是优雅的关闭,而不是暴力的停掉当前任务,线程退出。
ok,那么这个TimeoutException,是指哪里的Timeout ,显然,和我之前的理解是冲突的,书中提供Future来实现取消
public static void timedRun(Runnable r,long timeout,TimeUnit unit){
Future<?> task=taskExec.submit(r);
try{
task.get(timeout,unit);
}catch(TimeoutException e){
// 接下来任务将被取消
}catch(ExecutionException e){
//如果在任务中抛出异常,那么重新抛出该异常
throw launderThrowable(e.getCause());
}finally{
//如果任务已经结束,那么执行取消操作也不会带来任何影响
task.cancel(true);
//如果任务正在运行,那么将被中断。这里中断是指 运行任务的线程将被中断,
//至于线程是否响应中断,以及如何响应中断是另外的事情了。
}
}
看了这段代码,你说这个Timeout 到底是哪里的Timeout,是的确有可能将任务取消了,但也有可能任务正运行呢,所以,这个的Timeout,就是你查这个运行任务的结果,如果没在规定时间内查出来,就Timeout。这对任务是否直接停止可不负责任。
好吧,再来看看get(long timeout,TimeUnit unit) 在future 中的源码吧
//这里是FutureTask 类
// state 是FutureTask的状态, 小于COMPLETING(工作中)的状态是NEW (创建)
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
//如果状态小于等于(工作中),且在等了timeout时间内,状态依然小于等于(工作中),那么抛出TimeoutException
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
//这里是对不同状态做出回应
return report(s);
}
好的,再来看看awaitDone(…)方法
这本质是对应多个线程都调用了该方法,怎么办?
所以你会看到WaitNode,并对ta 产生疑惑,ta就是将线程排队
//这是WaitNode 类的方法
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
// 响应中断,去掉节点q
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // cannot time out yet
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
//用链表将所有的WaitNode,链在一起。
//注意这个用法,这里的waiters是头节点对象,q.next=waiters,q指向waiters对象,
//同时,又将waiters的指针指向q
/*
获取直接地址
waitersOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("waiters"));
like
waiters 代表对象
waitersOffset 代表指针
现在q的next 指向了waiters的对象,
然后,waiterOffset 指向了q
比如 一开始 waiters为null
现在 q.next=null
然后,waiters=q。
就是往头节点不断新增节点
而头节点永远指向最新的节点
*/
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
else if (timed) {
//计算时间
nanos = deadline - System.nanoTime();
//时间到了。
if (nanos <= 0L) {
//去掉节点q
removeWaiter(q);
//返回现有状态
return state;
}
//时间没到,那就让线程休息,
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
其中的removeWaiter(…),就是将node 的thread 设置为null。
private void removeWaiter(WaitNode node) {
if (node != null) {
node.thread = null;
retry:
for (;;) { // restart on removeWaiter race
for (WaitNode pred = null, q = waiters, s; q != null; q = s) {
s = q.next;
// q.thread ==null,则pred 就是前一个WaitNode
if (q.thread != null)
pred = q;
// 如果pred 不为null,
else if (pred != null) {
//pred的下一个节点就是s,去掉了当前q
//正是由于链表的新增只发生在头节点,这个才能安全的删掉q
pred.next = s;
//如果pred 也被设置为null,那就从retry循环一次
if (pred.thread == null) // check for race
continue retry;
}
//如果pred为null,且 q.thead==null,那说明q是头节点啊。
//那么就将waiters 赋予s的值,自然就去掉了q。
//头节点发生竞争,所有,这个要用cas
else if (!UNSAFE.compareAndSwapObject(this, waitersOffset,q, s))
continue retry;
}
break;
}
}
}
好了。done。源码对细节的把控真的很到位啊。
从源码也可以看出,Timeout是针对查询结果的线程抛出了TimeoutException。线程去查询是否有结果,没有就park(nanos),然后再去查询是否有结果,还没有且时间到了抛出TimeoutException。