使用 newSingleThreadExecutor 消除 ExecutorService 中的竞争条件

Eliminate race condition in ExecutorService with newSingleThreadExecutor

为了理解赛车我写了下面的程序:

import java.util.concurrent.*;

class RaceCount
{
    static int count = 0;

    public static void main(String [] args)
    {        
        ExecutorService executor = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 1000; i++)
        {
            executor.submit(new Callable<String>() {
                public String call() throws Exception {
                    count++; return "Incremented";
                }
            });
        }
        executor.shutdown();
        System.out.println(count);
    }
}

显然,计数远小于 1000。因此,我将 call() 方法签名更改为:

public synchronized String call() throws Exception {

但是,结果仍然小于 1000。如果我使用 newFixedThreadExecutor(1000) 而不是 newSingleThreadExecutor,那么我得到预期为 1000,即使 call() 方法没有以 synchronized 关键字作为前缀。
所以,我的查询是:
1.如何在newSingleThreadExecutor?
情况下同步线程 2、为什么不用同步,用了newFixedThreadExecutor

您的问题不是由于竞争条件引起的。它的发生仅仅是因为 executor.shutdown() 在返回之前没有等待完全关闭。

这是来自 java.util.concurrent.ExecutorService.shutdown():

的 javadocs

...
This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that.

换句话说,System.out.println(count)在一些任务运行之前是运行ning(尽管在所有任务提交之后必然是运行s)。

我对您的代码做了一点小改动,以证明这一点:

public static void main(String[] args) {
    ExecutorService executor = Executors.newSingleThreadExecutor();

    for (int i = 0; i < 1000; i++) {
        int e = i;
        executor.submit(new Callable<String>() {
            public String call() throws Exception {
                System.out.println("Executing " + e);
                count++;
                return "Incremented";
            }
        });
    }
    executor.shutdown();
    System.out.println("Count: " + count);
}

输出如下:

...
Executing 835
Executing 836
Executing 837
Count: 837     <----- Printed before all tasks are run
Executing 838
Executing 839
Executing 840
Executing 841
...

这清楚地表明任务在您读取 count 变量后继续 运行ning。

如果你需要在读取更新值之前确保任务被执行,那么你可能需要使用awaitTermination,如下:

executor.shutdown();
executor.awaitTermination(3, TimeUnit.SECONDS); //Pick an appropriate timeout value
System.out.println("Count: " + count);

关于关机的部分只是解决方案的一半。 "public synchronized String call()" 这会同步调用,以便只有一个线程可以同时执行一个实例的调用,但是对于 "executor.submit(new Callable() " 你有 1000 个调用实例。所以实际上没有同步。 您可以在循环外将其更改为 "Callable call = new Callable()..."。并且 "executor.submit(call);" 在里面,这样你就有了一个同步的调用实例。或者从 "int i" 更改为 "AtomicInteger i",从 ++i 更改为 i.incrementAndGet();