【问题标题】:future.isDone returns false even if the task is done即使任务完成,future.isDone 也会返回 false
【发布时间】:2012-03-07 15:52:07
【问题描述】:

我有一个棘手的情况,即使线程完成,future.isDone() 是否返回false。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class DataAccessor {
    private static ThreadPoolExecutor executor;
    private int timeout = 100000;
    static {
        executor = new ThreadPoolExecutor(10, 10, 1000, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(1000));
    }

    public static void main(String[] args) {
        List<String> requests = new ArrayList<String>();
        for(int i=0; i<20; i++){
            requests.add("request:"+i);
        }
        DataAccessor dataAccessor = new DataAccessor();

        List<ProcessedResponse> results = dataAccessor.getDataFromService(requests);
        for(ProcessedResponse response:results){
            System.out.println("response"+response.toString()+"\n");
        }
        executor.shutdown();
    }

    public List<ProcessedResponse> getDataFromService(List<String> requests) {
        final CountDownLatch latch = new CountDownLatch(requests.size());
        List<SubmittedJob> submittedJobs = new ArrayList<SubmittedJob>(requests.size());
        for (String request : requests) {
            Future<ProcessedResponse> future = executor.submit(new GetAndProcessResponse(request, latch));
            submittedJobs.add(new SubmittedJob(future, request));
        }
        try {
            if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
                // some of the jobs not done
                System.out.println("some jobs not done");
            }
        } catch (InterruptedException e1) {
            // take care, or cleanup
            for (SubmittedJob job : submittedJobs) {
                job.getFuture().cancel(true);
            }
        }
        List<ProcessedResponse> results = new LinkedList<DataAccessor.ProcessedResponse>();
        for (SubmittedJob job : submittedJobs) {
            try {
                // before doing a get you may check if it is done
                if (!job.getFuture().isDone()) {
                    // cancel job and continue with others
                    job.getFuture().cancel(true);
                    continue;
                }
                ProcessedResponse response = job.getFuture().get();
                results.add(response);
            } catch (ExecutionException cause) {
                // exceptions occurred during execution, in any
            } catch (InterruptedException e) {
                // take care
            }
        }
        return results;
    }

    private class SubmittedJob {
        final String request;
        final Future<ProcessedResponse> future;

        public Future<ProcessedResponse> getFuture() {
            return future;
        }

        public String getRequest() {
            return request;
        }

        SubmittedJob(final Future<ProcessedResponse> job, final String request) {
            this.future = job;
            this.request = request;
        }
    }

    private class ProcessedResponse {
        private final String request;
        private final String response;

        ProcessedResponse(final String request, final String response) {
            this.request = request;
            this.response = response;
        }

        public String getRequest() {
            return request;
        }

        public String getResponse() {
            return response;
        }

        public String toString(){
            return "[request:"+request+","+"response:"+ response+"]";
        }
    }

    private class GetAndProcessResponse implements Callable<ProcessedResponse> {
        private final String request;
        private final CountDownLatch countDownLatch;

        GetAndProcessResponse(final String request, final CountDownLatch countDownLatch) {
            this.request = request;
            this.countDownLatch = countDownLatch;
        }

        public ProcessedResponse call() {
            try {
                return getAndProcessResponse(this.request);
            } finally {
                countDownLatch.countDown();
            }
        }

        private ProcessedResponse getAndProcessResponse(final String request) {
            // do the service call
            // ........
            if("request:16".equals(request)){
                throw (new RuntimeException("runtime"));
            }
            return (new ProcessedResponse(request, "response.of." + request));
        }
    }
}

如果我调用future.isDone(),它会返回false,尽管coundownLatch.await() 返回true。任何的想法?另请注意,发生这种情况时 countDownLatch.await 会立即出现。

如果您在此处发现格式不可读的视图,http://tinyurl.com/7j6cvep。

【问题讨论】:

  • 您的代码没有意义。 pooledExecutor.submit(LCallable) 不起作用,你在哪里实例化 Callable?你在哪里创建闩锁,你在哪里等待它?
  • 我怀疑可调用对象可能会抛出未捕获的异常,从而中止工作线程,但在退出 call 之前也会倒计时。
  • @PéterTörök 哪个不属于 ExecutionException ?你能解释一下吗?
  • 只有在调用 Future.get() 时才会收到 ExecitionException。我会使用 for(Future&lt;Response&gt; future: futures) future.get(); 而不是使用倒计时锁存器或检查 isDone()。
  • @yadab,从那以后我也检查了 Javadoc,它确实表明我的怀疑是错误的。我仍然建议在使用线程时以某种方式处理异常。

标签: java multithreading future


【解决方案1】:

问题很可能是时间问题之一。闩锁将在之前释放所有关于 Future 的任务实际上完成(因为 countDown() 调用在 call() 方法内)。

您基本上是在重新创建 CompletionService 的工作(实现是 ExecutorCompletionService),我建议改用它。您可以使用poll(timeout) 方法获取结果。只需跟踪总时间,并确保将每次调用的超时时间减少到总剩余时间。

【讨论】:

  • 那我应该在哪里调用 countDown() 呢?
  • countDown 处于正确位置,但您不能依赖 CountdownLatch 与 Future 同步。他们有自己独立的同步控制。也就是说,CountDownLatch 可能已经让所有各方都到达了,但 FutureTask 还没有收到完成的信号。你需要一个或另一个。
  • @JohnVint 所以,我不检查future.IsDone,而是直接调用future.get(dummyTimeout) ?还是有另一种好方法来做到这一点?你能在单独的答案中解释吗?
  • @yadab 我肯定会的。但是,如果您打算接受一个,这个答案确实回答了您的问题。
【解决方案2】:

正如 jtahlborn 所提到的,这可能是一个竞争条件,其中 CountdownLatch 向其等待线程发出信号,等待线程在 FutureTask 完成执行之前评估 Future 的取消条件(这将在 countDown 之后的某个时间点发生)。

您根本不能依赖 CountdownLatch 的同步机制与 Future 的同步机制同步。您应该做的是依靠 Future 告诉您何时完成。

您可以Future.get(long timeout, TimeUnit.MILLISECONDS) 代替CountdownLatch.await(long timeout, TimeUnit.MILLISECONDS)。要获得与闩锁相同类型的效果,您可以将所有 Futures 添加到 List,遍历列表并获取每个 Future。

【讨论】:

    【解决方案3】:

    这是比赛条件的场景:

    • 主线程在latch.await,它在几毫秒内没有收到来自Java调度程序的CPU槽
    • 最后一个执行线程在finally子句中调用countDownLatch.countDown()
    • Java 调度程序决定为主线程提供更多优先级,因为它等待了一段时间
    • 结果,当它请求最后一个Future结果时,它还不可用,因为最后一个执行线程没有时间片来传播结果,它仍然在finally...

    我还没有找到关于 Java 调度程序如何真正工作的详细解释,可能是因为它主要取决于运行 JVM 的操作系统,但一般来说,它试图平均在一段时间内将 CPU 分配给可运行线程。这就是为什么主线程可以在另一个线程离开finally 子句之前到达isDone 测试。

    我建议您在latch.await 之后更改您的结果收集。如您所知,锁存器已降至零(除非主线程被中断),所有结果应该很快就会可用。带超时的 get 方法让调度程序有机会为 finally 子句中仍在等待的最后一个线程分配时间片:

        for (SubmittedJob job : submittedJobs) {
            try {
                ProcessedResponse response = null;
                try {
                    // Try to get answer in short timeout, should be available
                    response = job.getFuture().get(10, TimeUnit.MILLISECONDS);
                } catch (TimeoutException te) {
                    job.getFuture().cancel(true);
                    continue;
                }
                results.add(response);
            } catch (ExecutionException cause) {
                // exceptions occurred during execution, in any
            } catch (InterruptedException e) {
                // take care
            }
        }
    

    备注:您的代码不现实,因为getAndProcessResponse 方法在不到一毫秒的时间内结束。在那里随机休眠,竞争条件不会那么频繁地出现。

    【讨论】:

      【解决方案4】:

      我赞同关于比赛条件的意见。 我建议忘记闩锁并使用 java.util.concurrent.ThreadPoolExecutor.awaitTermination(long, TimeUnit)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2013-03-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-14
        • 1970-01-01
        • 2017-10-20
        相关资源
        最近更新 更多