使用 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();
为了理解赛车我写了下面的程序:
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()
:
...
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();