Java中如何实现线程的超时中断

之前在实现熔断降级组件时 , 需要实现一个接口的超时中断 , 意思是 , 业务在使用熔断降级功能时 , 在平台上设置了一个超时时间 , 如果在请求进入熔断器开始计时 , 并且接口在超时时间内没有响应 , 则需要提早中断该请求并返回 。
比如正常下游接口的超时时间为800ms , 但是因为自身业务的特殊需求 , 最多只能等200ms , 如果200ms之内没有数据返回 , 则返回降级数据 。这里处理请求的线程可以看成是Tomcat线程池中的一个线程 , 如果通过线程池返回的Future , 可以很轻松的实现超时返回 , 但是这种情况下 , 并不能拿到Futrue , 需要换一种思路 。

Java中如何实现线程的超时中断

文章插图
 
思路
中断一个线程的思路有哪些?
除了已经废弃的Thread.stop, Thread.suspend, Thread.resume 方法 , 剩下的貌似只有一种方案了 , 就是调用当前线程的interrupt() , 但是这个方法的作用并不是中断线程 , 而是设置一个标识 , 通知该线程可以被中断了 , 到底是继续执行 , 还是中断返回 , 由线程本身自己决定 。
具体来说 , 当对一个线程调用了interrupt()之后 , 如果该线程处于被阻塞状态(比如执行了wait、sleep或join等方法) , 那么会立即退出阻塞状态 , 并抛出一个InterruptedException异常 , 在代码中catch这个异常进行后续处理 。如果线程一直处于运行状态 , 那么只会把该线程的中断标志设置为 true , 仅此而已 , 所以interrupt()并不能真正的中断线程 , 不过在rpc调用的场景中 , 请求线程一般都处于阻塞状态 , 等待数据返回 , 这时interrupt()方法是可以派上用场的 。
那么 , 要实现指定超时时间内中断请求线程 , 还有最后一个问题需要解决:什么时候 , 由谁去执行interrupt()方法?
必然这个方法只能由其它线程来执行了(自己都阻塞了 , 执行个鬼) , 而且是在请求进入熔断器时 , 并在超时时间之后执行 , 有点绕 , 比如超时时间是200ms , 那么请求进入熔断器之后 , 再过200ms , 就执行interrupt() , 但是在200ms之内有数据返回 , 那么就不执行interrupt()了 。
实现
需求已经很明确了 , 相当于延迟执行一个task , 其内部逻辑就是执行请求线程的interrupt() , 当然还有其它的逻辑 。
Runnable task = new Runnable() {
@Override
public void run() {
try {
thread.interrupt();
// 取消定时器任务
f.cancel();
} catch (Exception e) {
【Java中如何实现线程的超时中断】logger.error("Failed while ticking TimerListener", e);
}
}
};
Doug Lea大神提供的ScheduledThreadPoolExecutor可以很好的满足这个需求 , 通过scheduleAtFixedRate方法可以很方便的实现在延迟指定时间之后执行提交的任务 。
ScheduledFuture<?> f = executor.scheduleAtFixedRate(task, timeout, timeout, TimeUnit.MILLISECONDS);在请求进入熔断器时 , 顺便提交一个任务到线程池中等待执行 , 如果接口在超时时间内没有返回 , 那么该任务会被触发 , 并执行请求线程的interrupt方法 , 这样就实现了请求线程的中断(因为这时请求线程正在被阻塞 , 等待数据返回) , 另外需要清空定时任务 , 不然这个任务会一直执行 。
如果接口正常返回了 , 也要记得清空定时任务 , 并且在请求退出熔断器的时候 , 记得恢复请求线程的中断标识 , 如何恢复?在请求线程中执行下面代码即可 。
Thread.interrupted();// 内部逻辑public static boolean interrupted() { return currentThread().isInterrupted(true);}// 参数为true , 可以清除中断标识private native boolean isInterrupted(boolean ClearInterrupted);执行当前线程(即请求线程)的isInterrupted方法 。
使用这种方式实现请求的超时中断 , 在QPS很高的情况下 , 会有额外的性能损失 , 因为每次请求都要提交一个任务到线程池中等待执行 。


推荐阅读