[英]How can I cancel the Future of a multi-threaded busy task?
在我的代碼中,我必須運行一個大量使用遞歸和並行流處理的任務,以便深入研究可能的游戲動作樹,並確定最佳動作。 這會花費很多時間,因此,為了防止用戶等待太長時間,計算機無法“思考”,我想將時間設置為1000毫秒。 如果在1000毫秒內找不到最佳移動,則計算機將隨機播放。 我的問題是,盡管我在Future上調用cancel(可能將中斷設置為true),但任務沒有被中斷,繁忙的線程仍在后台運行。 我試圖定期檢查當前的isInterrupted(),然后嘗試紓困,但這沒有幫助。 有任何想法嗎?
下面是我的代碼:
public Move bestMove() {
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Move> callable = () -> bestEntry(bestMoves()).getKey();
Future<Move> future = executor.submit(callable);
try {
return future.get(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
System.exit(0);
} catch (ExecutionException e) {
throw new RuntimeException(e);
} catch (TimeoutException e) {
future.cancel(true);
return randomMove();
}
return null;
}
private Move randomMove() {
Random random = new Random();
List<Move> moves = state.possibleMoves();
return moves.get(random.nextInt(moves.size()));
}
private <K> Map.Entry<K, Double> bestEntry(Map<K, Double> map) {
List<Map.Entry<K, Double>> list = new ArrayList<>(map.entrySet());
Collections.sort(list, (e1, e2) -> (int) (e2.getValue() - e1.getValue()));
return list.get(0);
}
private <K> Map.Entry<K, Double> worstEntry(Map<K, Double> map) {
List<Map.Entry<K, Double>> list = new ArrayList<>(map.entrySet());
Collections.sort(list, (e1, e2) -> (int) (e1.getValue() - e2.getValue()));
return list.get(0);
}
private Map<Move, Double> bestMoves() {
Map<Move, Double> moves = new HashMap<>();
state.possibleMoves().stream().parallel().forEach(move -> {
if (!Thread.currentThread().isInterrupted()) {
Game newState = state.playMove(move);
Double score = newState.isTerminal() ? newState.utility()
: worstEntry(new (newState).worstMoves()).getValue();
moves.put(move, score);
}
});
return moves;
}
private Map<Move, Double> worstMoves() {
Map<Move, Double> moves = new HashMap<>();
state.possibleMoves().stream().parallel().forEach(move -> {
if (!Thread.currentThread().isInterrupted()) {
Game newState = state.playMove(move);
Double score = newState.isTerminal() ? -newState.utility()
: bestEntry(new (newState).bestMoves()).getValue();
moves.put(move, score);
}
});
return moves;
}
ps:我也嘗試了沒有“ parallel()”的情況,但是仍然有一個線程在運行。
提前致謝。
Future.cancel
只是將線程設置為interrupted
,然后您的代碼必須按以下方式對其進行處理:
public static void main(String[] args) throws InterruptedException {
final ExecutorService executor = Executors.newSingleThreadExecutor();
final Future<Integer> future = executor.submit(() -> count());
try {
System.out.println(future.get(1, TimeUnit.SECONDS));
} catch (Exception e){
future.cancel(true);
e.printStackTrace();
}finally {
System.out.printf("status=finally, cancelled=%s, done=%s%n", future.isCancelled(), future.isDone());
executor.shutdown();
}
}
static int count() throws InterruptedException {
while (!Thread.interrupted());
throw new InterruptedException();
}
如您所見, count
一直在檢查線程是否可用於繼續運行,因此您必須了解,實際上並不能保證如果不想運行線程就可以停止。
參考:
更新 2017-11-18 23:22
我編寫了FutureTask擴展,即使代碼不遵守interrupt
信號,它也可以嘗試停止Thread。 請記住,這是不安全的,因為Thread.stop
方法已被棄用 ,無論如何它都可以正常工作,並且如果您確實需要可以使用它(例如,如果您使用鎖,請先閱讀Thread.stop棄用說明,然后運行.stop可能會導致死鎖)。
測試碼
public static void main(String[] args) throws InterruptedException {
final ExecutorService executor = newFixedSizeExecutor(1);
final Future<Integer> future = executor.submit(() -> count());
try {
System.out.println(future.get(1, TimeUnit.SECONDS));
} catch (Exception e){
future.cancel(true);
e.printStackTrace();
}
System.out.printf("status=finally, cancelled=%s, done=%s%n", future.isCancelled(), future.isDone());
executor.shutdown();
}
static int count() throws InterruptedException {
while (true);
}
海關執行人
static ThreadPoolExecutor newFixedSizeExecutor(final int threads) {
return new ThreadPoolExecutor(threads, threads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>()){
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new StoppableFutureTask<>(new FutureTask<>(callable));
}
};
}
static class StoppableFutureTask<T> implements RunnableFuture<T> {
private final FutureTask<T> future;
private Field runnerField;
public StoppableFutureTask(FutureTask<T> future) {
this.future = future;
try {
final Class clazz = future.getClass();
runnerField = clazz.getDeclaredField("runner");
runnerField.setAccessible(true);
} catch (Exception e) {
throw new Error(e);
}
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
final boolean cancelled = future.cancel(mayInterruptIfRunning);
if(cancelled){
try {
((Thread) runnerField.get(future)).stop();
} catch (Exception e) {
throw new Error(e);
}
}
return cancelled;
}
@Override
public boolean isCancelled() {
return future.isCancelled();
}
@Override
public boolean isDone() {
return future.isDone();
}
@Override
public T get() throws InterruptedException, ExecutionException {
return future.get();
}
@Override
public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return future.get(timeout, unit);
}
@Override
public void run() {
future.run();
}
}
輸出
java.util.concurrent.TimeoutException
at java.util.concurrent.FutureTask.get(FutureTask.java:205)
at com.mageddo.spark.sparkstream_1.Main$StoppableFutureTask.get(Main.java:91)
at com.mageddo.spark.sparkstream_1.Main.main(Main.java:20)
status=finally, cancelled=true, done=true
Process finished with exit code 0
謝謝大家的答案。 我想我找到了一個更簡單的解決方案。 首先,我認為future.cancel(true)不起作用的原因是,它可能僅在啟動任務的線程上設置了中斷標志。 (即與將來關聯的線程)。 但是,由於任務本身使用並行流處理,因此會在永不中斷的不同線程上生成工作線程,因此,我無法定期檢查isInterrupted()標志。 我發現的“解決方案”(或者更多的解決方法)是將自己的中斷標志保留在算法的對象中,並在取消任務時手動將其設置為true。 因為所有線程都在同一個實例上運行,所以它們都可以訪問中斷標志,並且它們服從。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.