题主最近遇到一个问题,本来通过
ScheduledExecutorService
线程池定时调度一个任务。奈何不知道为啥跑了2个多月,其中一个任务Hang住了,原本定时的任务则出现了问题。
关于定时线程池,好多人认为设置好频率(比如1Min),它会按照这个间隔按部就班的工作。但是,如果其中一次调度任务卡住的话,不仅这次调度失败,而且整个线程池也会停在这次调度上。
我们先从一个例子试着复现下问题:
public class pool {
private static class Runner implements Runnable {
@Override
public void run() {
try {
Thread.sleep(10000);
System.out.println(new Date());
} catch (Exception e) {
e.printStackTrace();
public static void main(String[] args) {
ScheduledExecutorService service
= Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(
new Runner(), 0, 1, TimeUnit.SECONDS);
先从Main
看,启动一个定时线程池,每隔1S调度一次Runner
。看上去,应该是1S调度一次,但是Runner
的实际执行时间为10S,那多久会调度一次?答案是10S。
所以说,这个Runner
不管什么原因挂掉了或者Hang住了,那这个定时调度线程池基本就废了。
二、解决方法
那我们应该怎么解决这个问题?如果说定时线程池有任务调度的超时策略就完美了,很可惜并没有。
我们想下在并发编程中,哪种方式有超时策略?
对,Future
有,那我们可以结合Future
,提供一种自动停止超时任务的方式,来解决某个任务Hang住的问题。
我们简单修改下,把sleep
逻辑移动到Callable
中,并在Runner
中使用Future
来控制超时。
public class pool {
private static class Caller implements Callable<Boolean> {
@Override
public Boolean call() {
try {
Thread.sleep(10000);
System.out.println(new Date());
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
private static class Runner implements Runnable {
@Override
public void run() {
ExecutorService excutor = Executors.newSingleThreadExecutor();
Future<Boolean> future = excutor.submit(new Caller());
try {
future.get(1, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.out.println("timeout");
} catch (Exception e) {
e.printStackTrace();
} finally {
excutor.shutdownNow();
public static void main(String[] args) {
ScheduledExecutorService service
= Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(
new Runner(), 0, 1, TimeUnit.SECONDS);
备注:
- 实现逻辑相当于转移了,把本来应该调度的任务交给了另外一个Future
单线程去执行。因为存在超时逻辑,不会影响原有定时线程池的执行。
- finally是否需要杀死线程池,因人而异。如果不杀死的话,那超时的任务会继续执行。
题外话:如果你有好的解决方式,欢迎和题主探讨。谢谢。
一、背景题主最近遇到一个问题,本来通过ScheduledExecutorService线程池定时调度一个任务。奈何不知道为啥跑了2个多月,其中一个任务Hang住了,那定时的任务则出现了问题。关于定时线程池,好多人任务设置好频率(比如1Min),它会按照这个间隔按部就班的工作。但是,如果其中一次调度任务卡住的话,不仅这次调度失败,而且整个线程池也会停在这次调度上。我们先从一个例子试着复现...
关于
线程超时没有太多的操作,现在只有一个简单的思路,那就是开启一个
线程轮训监听所有
线程池。
刚好最近在做一个项目,需要一对多进行轮训和监听,需要用到
超时处理,因此特意看了一下,关键的就是开启
线程,轮训查询
线程执行时间,
超时了直接关闭,也可以选择重开
线程。
class TEST{
private static final int MAX_WAIT_TIME = 8000;
线程池就是一个可以复用线程的技术。前面三种多线程方法就是在用户发起一个线程请求就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。线程池就相当于预先创建好几个线程(招聘几个打工人),来分配之后要处理的任务(干活)。
线程池的接口:ExecutorService
线程池对象
1. 使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
2. 使用Executors(线程池的工具类)调用方法返回不同特点的线程池
解决线程的死掉问题和超时问题特别好使,在Java中,如果需要设定代码执行的最长时间,即超时,可以用Java线程池ExecutorService类配合Future接口来实现。 Future接口是Java标准API的一部分,在java.util.concurrent包中。Future接口是Java线程Future模式的实 现,可以来进行异步计算。
Future模式可以这样来描述:我有一个任务,提交给了Future,Future替我完成这个任务。期间我自己可以去做任何想做的事情。一段时 间之后,我就便可以从Future那儿取出结果。就相当于下了一张订货单,一段时间后可以拿着提订单来提货,这期间可以干别的任何事情。其中Future 接口就是订货单,真正处理订单的是Executor类,它根据Future接口的要求来生产产品。
Future接口提供方法来检测任务是否被执行完,等待任务执行完获得结果,也可以设置任务执行的超时时间。这个设置超时的方法就是实现Java程 序执行超时的关键。
Future接口是一个泛型接口,严格的格式应该是Future,其中V代表了Future执行的任务返回值的类型。 Future接口的方法介绍如下:
boolean cancel(boolean mayInterruptIfRunning) 取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束
boolean isCancelled() 任务是否已经取消,任务正常完成前将其取消,则返回true
boolean isDone() 任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true
V get() throws InterruptedException, ExecutionException 等待任务执行结束,然后获得V类型的结果。InterruptedException 线程被中断异常, ExecutionException任务执行异常,如果任务被取消,还会抛出CancellationException
V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException 同上面的get功能一样,多了设置超时时间。参数timeout指定超时时间,uint指定时间的单位,在枚举类TimeUnit中有相关的定义。如果计 算超时,将抛出TimeoutException
Future的实现类有java.util.concurrent.FutureTask即 javax.swing.SwingWorker。通常使用FutureTask来处理我们的任务。FutureTask类同时又 实现了Runnable接口,所以可以直接提交给Executor执行。使用FutureTask实现超时执行的代码如附件:FutureTaskAndExcutor.java
不直接构造Future对象,也可以使用ExecutorService.submit方法来获得Future对象,submit方法即支持以 Callable接口类型,也支持Runnable接口作为参数,具有很大的灵活性。使用示例如FutureTaskAndExcutor中的limitDemo2方法。
启动一个线程可以调用start();那么停止一个线程可以怎么处理呢?
1、interrupt()
最正确的停止线程的方式是使用 interrupt。但 interrupt仅仅起到通知被停止线程的作用。而对于被停止的线程而言,它拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择不停止。
2、shutdown()
调用 shutdown() 方法之后线程池不会立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown()
1.使用violate boolean变量退出标志,使
线程正常退出,即
线程执行完run方法体后,正常退出(推荐)。
2.使用interrupt() 尝试终止
线程,但是不一定成功。
3.使用stop()强制终止
线程。不安全的是:创建子
线程的
线程会抛出异常,并且会释放子
线程持有的所有的锁。
2.终止
线程池
1.shutdown()关闭
线程池
线程池不会立马关闭,要等添加到
线程池的
任务都执行完了,才会关闭。
2.shutdownNow()关闭
线程池并终止
任务
终止等待执行的
线程,并
这一节介绍并发编程中如何对任务执行取消操作。java中没有提供停止线程的方法,只用通过协作——使代码遵循一定的协议来请求取消。
一种常见的取消任务的办法是轮询检查。线程在执行的过程中通过定期检查一个boolean变量来决定是否终止任务。下面是一个使用轮询检查来终止素数生成的例子:
public class PrimeGenerator implements Runnable {
private static ExecutorService exec = Executors.newCache
使用Thread.join(long million)方法,参数是毫秒
代码&解析如下:
解析:原本有t1和t2两个线程,根据实例化new Task()时t1传入了4,t2传入了2,分别相当于t1需要执行4次,t2需要执行2次,但是在run方法中使用了Thread.sleep(1000),所以t1执行4次就等于是执行4秒,t2同理。在t1.start启动后,调用了join方法设置了两秒的参数,相当于在t1执行两秒后就超时了,后面就是t1超时后的设置,
1、t1.interrupt()表示打断t1线程
要在Java中实现订单超时功能,你可以使用Java的`java.util.Timer`和`java.util.TimerTask`类。
你可以使用`Timer`类来安排一个任务,在指定的延迟之后执行,或者安排任务在固定的延迟和间隔频率后重复执行。
你可以使用`TimerTask`类来实现你的订单超时任务。`TimerTask`是一个抽象类,你需要创建一个它的子类,并重写`run()`方法。在`run()`方法中,你可以实现对订单超时的处理逻辑。
import java.util.Timer;
import java.util.TimerTask;
public class OrderTimeoutTask extends TimerTask {
// 订单超时时间
private long timeout;
// 订单ID
private long orderId;
public OrderTimeoutTask(long timeout, long orderId) {
this.timeout = timeout;
this.orderId = orderId;
@Override
public void run() {
// 处理订单超时逻辑
// ...
Timer timer = new Timer();
long timeout = 60 * 60 * 1000; // 1小时
long orderId = 123456;
timer.schedule(new OrderTimeoutTask(timeout, orderId), timeout);
在上面的代码中,我们创建了一个`OrderTimeoutTask`任务,并使用`Timer`类安排它在1小时之后执行。在`run()`方法中,你可以实现对超时订单的处理逻辑。
注意:如果你想在多个订单之间共享同一个`Timer`对象,你需要注意线程安全问题。`Timer`是非线程安全的,如果多个线程同时