我正在寻找可以提供超时的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();
}
}
}
答案 0 :(得分:78)
您可以使用ScheduledExecutorService。首先,您只需提交一次即可立即开始并保留创建的未来。之后,您可以提交一项新任务,在一段时间后取消保留的未来。
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秒,然后取消(即中断)该特定任务。
答案 1 :(得分:5)
不幸的是,解决方案存在缺陷。 ScheduledThreadPoolExecutor
存在一种错误,也在this question中报告:取消提交的任务并不能完全释放与任务相关的内存资源;只有在任务到期时才会释放资源。
如果您因此创建了一个具有相当长的到期时间(典型用法)的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,并且最近似乎已修复。
答案 2 :(得分:4)
在FutureTask中包装任务,您可以为FutureTask指定超时。请看我对这个问题的答案中的例子,
答案 3 :(得分:1)
如何使用http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html中描述的ExecutorService.shutDownNow()
方法?这似乎是最简单的解决方案。
答案 4 :(得分:1)
似乎问题不在JDK bug 6602600(它在2010-05-22解决),但在 圈内不正确的睡眠呼叫(10)。另外注意,主线程必须给出 通过调用SLEEP(0)直接CHANCE到其他线程来实现他们的任务 外圈的每个分支。 我认为最好使用Thread.yield()而不是Thread.sleep(0)
以前的问题代码的结果更正部分是这样的:
.......................
........................
Thread.yield();
if (i % 1000== 0) {
System.out.println(i + "/" + counter.get()+ "/"+service.toString());
}
//
// while (i > counter.get()) {
// Thread.sleep(10);
// }
它可以正常使用外部计数器,最多可达150,000个测试圆圈。
答案 5 :(得分:1)
经过大量的时间调查,
最后,我使用invokeAll
ExecutorService
方法解决了这个问题
这将在任务运行时严格中断任务
这是示例
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();
专业人士也可以在同一ListenableFuture
提交ExecutorService
只需稍微更改第一行代码即可。
ListeningExecutorService executorService = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListeningExecutorService
是google guava项目中ExecutorService
的听力功能( com.google.guava ))
答案 6 :(得分:1)
使用John W回答我创建了一个在任务开始执行时正确开始超时的实现。我甚至为它写了一个单元测试:)
但是,它不适合我的需要,因为一些IO操作在调用Future.cancel()
时不会中断(即调用Thread.interrupted()
时)。
调用Thread.interrupted()
时可能不会中断的IO操作的一些示例是Socket.connect
和Socket.read
(我怀疑大部分IO操作都在java.io
中实现)。调用java.nio
时,Thread.interrupted()
中的所有IO操作都应该是可中断的。例如,SocketChannel.open
和SocketChannel.read
就是这种情况。
无论如何,如果有人感兴趣,我为线程池执行器创建了一个要点,允许任务超时(如果他们使用可中断的操作......):https://gist.github.com/amanteaux/64c54a913c1ae34ad7b86db109cbc0bf
答案 7 :(得分:0)
这个替代想法怎么样:
小样本在这里:
Derived*
答案 8 :(得分:0)
检查是否适合您
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;
}
您可以限制调度程序中线程的使用数量,也可以限制任务的超时时间。
答案 9 :(得分:0)
您可以使用 ExecutorService 提供的这个实现
invokeAll(Collection<? extends Callable<T>> tasks,long timeout, TimeUnit unit)
as
executor.invokeAll(Arrays.asList(task), 2 , TimeUnit.SECONDS);
但是,就我而言,我不能,因为 Arrays.asList 需要额外的 20 毫秒。
答案 10 :(得分:0)
这个怎么样?
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
}