在超时后中断任务的 ExecutorService

我正在寻找一个 执行服务实现,可以提供一个超时。如果提交到 ExecutorService 的任务运行时间超过超时时间,则会中断这些任务。实现这样一个野兽并不是一项困难的任务,但我想知道是否有人知道现有的实现。

以下是我根据下面的讨论得出的结论。有什么意见吗?

import java.util.List;
import java.util.concurrent.*;


public class TimeoutThreadPoolExecutor extends ThreadPoolExecutor {
private final long timeout;
private final TimeUnit timeoutUnit;


private final ScheduledExecutorService timeoutExecutor = Executors.newSingleThreadScheduledExecutor();
private final ConcurrentMap<Runnable, ScheduledFuture> runningTasks = new ConcurrentHashMap<Runnable, ScheduledFuture>();


public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, long timeout, TimeUnit timeoutUnit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
this.timeout = timeout;
this.timeoutUnit = timeoutUnit;
}


public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, long timeout, TimeUnit timeoutUnit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
this.timeout = timeout;
this.timeoutUnit = timeoutUnit;
}


public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
this.timeout = timeout;
this.timeoutUnit = timeoutUnit;
}


public TimeoutThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler, long timeout, TimeUnit timeoutUnit) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
this.timeout = timeout;
this.timeoutUnit = timeoutUnit;
}


@Override
public void shutdown() {
timeoutExecutor.shutdown();
super.shutdown();
}


@Override
public List<Runnable> shutdownNow() {
timeoutExecutor.shutdownNow();
return super.shutdownNow();
}


@Override
protected void beforeExecute(Thread t, Runnable r) {
if(timeout > 0) {
final ScheduledFuture<?> scheduled = timeoutExecutor.schedule(new TimeoutTask(t), timeout, timeoutUnit);
runningTasks.put(r, scheduled);
}
}


@Override
protected void afterExecute(Runnable r, Throwable t) {
ScheduledFuture timeoutTask = runningTasks.remove(r);
if(timeoutTask != null) {
timeoutTask.cancel(false);
}
}


class TimeoutTask implements Runnable {
private final Thread thread;


public TimeoutTask(Thread thread) {
this.thread = thread;
}


@Override
public void run() {
thread.interrupt();
}
}
}
123866 次浏览

Wrap the task in FutureTask and you can specify timeout for the FutureTask. Look at the example in my answer to this question,

本机进程超时

您可以使用 计划执行服务进行此操作。首先,你只需要提交一次就可以立即开始并保留你创造的未来。之后,您可以提交一个新任务,该任务将在一段时间后取消保留的未来。

 ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
final Future handler = executor.submit(new Callable(){ ... });
executor.schedule(new Runnable(){
public void run(){
handler.cancel();
}
}, 10000, TimeUnit.MILLISECONDS);

这将执行您的处理程序(主要功能将被中断)10秒钟,然后将取消(即中断)该特定任务。

不幸的是,解决方案是有缺陷的。ScheduledThreadPoolExecutor有一种 bug,也在 这个问题中报告过: 取消提交的任务不会完全释放与该任务相关的内存资源; 资源只有在任务到期时才会释放。

因此,如果您创建了一个过期时间相当长的 TimeoutThreadPoolExecutor(一种典型的用法) ,并且足够快地提交任务,那么您最终将填满内存-即使任务实际上已经成功完成。

你可以看到下面这个(非常粗糙的)测试程序的问题:

public static void main(String[] args) throws InterruptedException {
ExecutorService service = new TimeoutThreadPoolExecutor(1, 1, 10, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), 10, TimeUnit.MINUTES);
//ExecutorService service = Executors.newFixedThreadPool(1);
try {
final AtomicInteger counter = new AtomicInteger();
for (long i = 0; i < 10000000; i++) {
service.submit(new Runnable() {
@Override
public void run() {
counter.incrementAndGet();
}
});
if (i % 10000 == 0) {
System.out.println(i + "/" + counter.get());
while (i > counter.get()) {
Thread.sleep(10);
}
}
}
} finally {
service.shutdown();
}
}

该程序耗尽可用内存,尽管它等待产生的 Runnable完成。

我想了一会儿,但不幸的是,我想不出一个好的解决办法。

更新

我发现这个问题被报告为 JDK bug 6602600,并且似乎已经在 Java 7中得到修复。

如何使用 http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html中描述的 ExecutorService.shutDownNow()方法? 这似乎是最简单的解决方案。

这个问题似乎不在 JDK bug 6602600中(它在2010-05-22中得到了解决) ,而是在 不正确的睡眠调用(10)在圆圈。补充说明,主线程必须给出 中调用 SLEEP (0)来实现其他线程的任务 外圈的每一个分支。 我认为最好使用 Thread.屈服()而不是 Thread.sleep (0)

前面的问题代码的结果更正部分是这样的:

.......................
........................
Thread.yield();


if (i % 1000== 0) {
System.out.println(i + "/" + counter.get()+ "/"+service.toString());
}


//
//                while (i > counter.get()) {
//                    Thread.sleep(10);
//                }

它正常工作的数量外部计数器多达150000000测试圆。

那么这个替代的想法是什么呢:

  • 两个有两个遗嘱执行人:
    • 一个是:
      • 提交任务,而不考虑任务的超时
      • adding the Future resulted and the time when it should end to an internal structure
    • 一种用于执行内部作业,该作业检查某些任务是否超时以及是否必须取消它们的内部结构。

小样本在这里:

public class AlternativeExecutorService
{


private final CopyOnWriteArrayList<ListenableFutureTask> futureQueue       = new CopyOnWriteArrayList();
private final ScheduledThreadPoolExecutor                scheduledExecutor = new ScheduledThreadPoolExecutor(1); // used for internal cleaning job
private final ListeningExecutorService                   threadExecutor    = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(5)); // used for
private ScheduledFuture scheduledFuture;
private static final long INTERNAL_JOB_CLEANUP_FREQUENCY = 1000L;


public AlternativeExecutorService()
{
scheduledFuture = scheduledExecutor.scheduleAtFixedRate(new TimeoutManagerJob(), 0, INTERNAL_JOB_CLEANUP_FREQUENCY, TimeUnit.MILLISECONDS);
}


public void pushTask(OwnTask task)
{
ListenableFuture<Void> future = threadExecutor.submit(task);  // -> create your Callable
futureQueue.add(new ListenableFutureTask(future, task, getCurrentMillisecondsTime())); // -> store the time when the task should end
}


public void shutdownInternalScheduledExecutor()
{
scheduledFuture.cancel(true);
scheduledExecutor.shutdownNow();
}


long getCurrentMillisecondsTime()
{
return Calendar.getInstance().get(Calendar.MILLISECOND);
}


class ListenableFutureTask
{
private final ListenableFuture<Void> future;
private final OwnTask                task;
private final long                   milliSecEndTime;


private ListenableFutureTask(ListenableFuture<Void> future, OwnTask task, long milliSecStartTime)
{
this.future = future;
this.task = task;
this.milliSecEndTime = milliSecStartTime + task.getTimeUnit().convert(task.getTimeoutDuration(), TimeUnit.MILLISECONDS);
}


ListenableFuture<Void> getFuture()
{
return future;
}


OwnTask getTask()
{
return task;
}


long getMilliSecEndTime()
{
return milliSecEndTime;
}
}


class TimeoutManagerJob implements Runnable
{
CopyOnWriteArrayList<ListenableFutureTask> getCopyOnWriteArrayList()
{
return futureQueue;
}


@Override
public void run()
{
long currentMileSecValue = getCurrentMillisecondsTime();
for (ListenableFutureTask futureTask : futureQueue)
{
consumeFuture(futureTask, currentMileSecValue);
}
}


private void consumeFuture(ListenableFutureTask futureTask, long currentMileSecValue)
{
ListenableFuture<Void> future = futureTask.getFuture();
boolean isTimeout = futureTask.getMilliSecEndTime() >= currentMileSecValue;
if (isTimeout)
{
if (!future.isDone())
{
future.cancel(true);
}
futureQueue.remove(futureTask);
}
}
}


class OwnTask implements Callable<Void>
{
private long     timeoutDuration;
private TimeUnit timeUnit;


OwnTask(long timeoutDuration, TimeUnit timeUnit)
{
this.timeoutDuration = timeoutDuration;
this.timeUnit = timeUnit;
}


@Override
public Void call() throws Exception
{
// do logic
return null;
}


public long getTimeoutDuration()
{
return timeoutDuration;
}


public TimeUnit getTimeUnit()
{
return timeUnit;
}
}
}

After ton of time to survey,
最后,我用 ExecutorServiceinvokeAll方法来解决这个问题。
这将在任务运行时严格中断任务。
这里有个例子

ExecutorService executorService = Executors.newCachedThreadPool();


try {
List<Callable<Object>> callables = new ArrayList<>();
// Add your long time task (callable)
callables.add(new VaryLongTimeTask());
// Assign tasks for specific execution timeout (e.g. 2 sec)
List<Future<Object>> futures = executorService.invokeAll(callables, 2000, TimeUnit.MILLISECONDS);
for (Future<Object> future : futures) {
// Getting result
}
} catch (InterruptedException e) {
e.printStackTrace();
}


executorService.shutdown();

优点是你也可以在相同的 ExecutorService上提交 ListenableFuture
稍微修改一下第一行代码。

ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());

ListeningExecutorService是在谷歌番石榴项目(Com.google.guava)的 ExecutorService倾听功能

使用 JohnW 答案,我创建了一个实现,该实现在任务开始执行时正确开始超时。我甚至为它编写了一个单元测试:)

但是,它不适合我的需要,因为一些 IO 操作在调用 Future.cancel()时(即在调用 Thread.interrupt()时)不会中断。 在调用 Thread.interrupt()时可能不会被中断的 IO 操作的一些例子是 Socket.connectSocket.read(我怀疑大多数 IO 操作是在 java.io中实现的)。当调用 Thread.interrupt()时,java.nio中的所有 IO 操作都应该是可中断的。例如,SocketChannel.openSocketChannel.read就是这种情况。

无论如何,如果有人感兴趣,我为一个线程池执行器创建了一个 gist,它允许任务超时(如果他们使用可中断操作...) : https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf

看看这个对你有没有用,

    public <T,S,K,V> ResponseObject<Collection<ResponseObject<T>>> runOnScheduler(ThreadPoolExecutor threadPoolExecutor,
int parallelismLevel, TimeUnit timeUnit, int timeToCompleteEachTask, Collection<S> collection,
Map<K,V> context, Task<T,S,K,V> someTask){
if(threadPoolExecutor==null){
return ResponseObject.<Collection<ResponseObject<T>>>builder().errorCode("500").errorMessage("threadPoolExecutor can not be null").build();
}
if(someTask==null){
return ResponseObject.<Collection<ResponseObject<T>>>builder().errorCode("500").errorMessage("Task can not be null").build();
}
if(CollectionUtils.isEmpty(collection)){
return ResponseObject.<Collection<ResponseObject<T>>>builder().errorCode("500").errorMessage("input collection can not be empty").build();
}


LinkedBlockingQueue<Callable<T>> callableLinkedBlockingQueue = new LinkedBlockingQueue<>(collection.size());
collection.forEach(value -> {
callableLinkedBlockingQueue.offer(()->someTask.perform(value,context)); //pass some values in callable. which can be anything.
});
LinkedBlockingQueue<Future<T>> futures = new LinkedBlockingQueue<>();


int count = 0;


while(count<parallelismLevel && count < callableLinkedBlockingQueue.size()){
Future<T> f = threadPoolExecutor.submit(callableLinkedBlockingQueue.poll());
futures.offer(f);
count++;
}


Collection<ResponseObject<T>> responseCollection = new ArrayList<>();


while(futures.size()>0){
Future<T> future = futures.poll();
ResponseObject<T> responseObject = null;
try {
T response = future.get(timeToCompleteEachTask, timeUnit);
responseObject = ResponseObject.<T>builder().data(response).build();
} catch (InterruptedException e) {
future.cancel(true);
} catch (ExecutionException e) {
future.cancel(true);
} catch (TimeoutException e) {
future.cancel(true);
} finally {
if (Objects.nonNull(responseObject)) {
responseCollection.add(responseObject);
}
futures.remove(future);//remove this
Callable<T> callable = getRemainingCallables(callableLinkedBlockingQueue);
if(null!=callable){
Future<T> f = threadPoolExecutor.submit(callable);
futures.add(f);
}
}


}
return ResponseObject.<Collection<ResponseObject<T>>>builder().data(responseCollection).build();
}


private <T> Callable<T> getRemainingCallables(LinkedBlockingQueue<Callable<T>> callableLinkedBlockingQueue){
if(callableLinkedBlockingQueue.size()>0){
return callableLinkedBlockingQueue.poll();
}
return null;
}

you can restrict the no of thread uses from scheduler as well as put timeout on the task.

您可以使用 ExecutorService 提供的此实现

invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
as


executor.invokeAll(Arrays.asList(task), 2 , TimeUnit.SECONDS);

但是,在我的例子中,Arrays.asList 需要额外的20ms。

这个怎么样?

final ExecutorService myExecutorService = ...;


// create CompletableFuture to get result/exception from runnable in specified timeout
final CompletableFuture<Object> timeoutFuture = new CompletableFuture<>();


// submit runnable and obtain cancellable Future from executor
final Future<?> cancellableFuture = myExecutorService.submit(() -> {
try {
Object result = myMethod(...);
timeoutFuture.complete(result);
} catch (Exception e) {
timeoutFuture.completeExceptionally(e);
}
});


// block the calling thread until "myMethod" will finish or time out (1 second)
try {
Object result = timeoutFuture.get(1000, TimeUnit.MILLISECONDS);
// "myMethod" completed normally
} catch (TimeoutException te) {
// "myMethod" timed out
// ...
} catch (ExecutionException ee) {
// "myMethod" completed exceptionally - get cause
final Throwable cause = ee.getCause();
// ...
} catch (InterruptedException ie) {
// future interrupted
// ...
} finally {
// timeoutFuture.cancel(true); // CompletableFuture does not support cancellation
cancellableFuture.cancel(true); // Future supports cancellation
}