关于java:future.isDone即使任务完成也返回false

future.isDone returns false even if the task is done

我遇到了棘手的情况,即使线程完成了,future.isDone()是否也会返回false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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()+"\
"
);
        }
        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。


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

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


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

您根本无法依靠CountdownLatch的同步机制与Future的同步机制进行同步。您应该做的就是依靠Future告诉您何时完成。

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


以下是比赛条件的场景:

  • 主线程在latch.await中,它在毫秒内没有从Java调度程序接收任何CPU插槽
  • 最后一个执行程序线程在finally子句中调用countDownLatch.countDown()
  • Java调度程序决定给予主线程更高的优先级,因为等待了一段时间
  • 结果,当它要求最后一个Future结果时,它尚不可用,因为最后一个执行程序线程没有时间片来传播结果,所以它仍在finally中。

我还没有找到有关Java调度程序实际工作方式的详细说明,可能是因为它主要取决于运行JVM的操作系统,但通常来说,它会平均尝试在一段时间内平均将CPU分配给可运行线程。这就是为什么主线程可以在另一个线程离开finally子句之前达到isDone测试的原因。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    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方法在不到一毫秒的时间内结束。在那里随机睡觉,种族状况不会经常出现。


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