是否ExecutorService
保证线程安全?
我将把来自不同线程的作业提交到同一个ThreadPoolExecutor,在交互/提交任务之前,我是否必须同步对执行者的访问?
Answers:
的确,有问题的JDK类似乎并未明确保证线程安全的任务提交。但是,实际上,库中的所有ExecutorService实现实际上都是以这种方式线程安全的。我认为依靠这一点是合理的。由于实现这些功能的所有代码都放置在公共领域中,因此任何人都完全没有动力以另一种方式完全重写它。
interface ExecutorService
,它ThreadPoolExecutor
也必须遵守。(我最近更新的答案中有更多详细信息。)
(相反,其他的答案)线程安全性合同的记载:看在interface
Javadoc中(而不是方法的javadoc)。例如,在ExecutorService javadoc的底部,您可以找到:
内存一致性影响:在将Runnable或Callable任务提交给ExecutorService之前,线程中的操作发生在该任务执行 的任何操作之前,而该操作又 发生在通过Future.get()检索结果之前。
这足以回答以下问题:
“在交互/提交任务之前,我是否必须同步对执行器的访问?”
不,你不会。ExecutorService
无需外部同步就可以构造作业并将其提交给任何(正确实施的)作业。这是主要的设计目标之一。
ExecutorService
是一个并发实用程序,也就是说,它是为了在不进行同步的情况下最大程度地运行以提高性能。(同步会导致线程争用,这可能会降低多线程效率,尤其是在扩展到大量线程时。)
有没有关于在任务将执行什么在未来的时间或完全担保(有的甚至会立即执行上提交他们相同的线程),然而工作线程是保证已经看到,提交线程执行的所有效果达投稿点。因此,您的任务(正在运行的线程)还可以安全地读取为使用而创建的任何数据,而无需同步,线程安全的类或任何其他形式的“安全发布”。提交任务的行为本身足以将输入数据“安全发布”到任务。您只需要确保在任务运行时不会以任何方式修改输入数据。
同样,当您通过取回任务的结果时Future.get()
,将确保检索线程可以看到执行者的工作线程所产生的所有效果(在返回的结果以及工作线程可能进行的任何副作用更改中) 。
该合同还意味着任务本身可以提交更多任务。
“ ExecutorService是否保证线程安全?”
现在,问题的这一部分更为笼统。例如,找不到关于该方法的任何线程安全性合同声明 shutdownAndAwaitTermination
-尽管我注意到Javadoc中的代码示例未使用同步。(尽管也许有一个隐藏的假设,认为关闭是由创建执行器的线程发起的,而不是由工人线程发起的?)
顺便说一句,我推荐本书“ Java Concurrency In Practice”作为并发编程领域的基础。
您的问题是开放式的:ExecutorService
接口的所有作用是确保某个地方的某个线程将处理提交的Runnable
或Callable
实例。
如果提交Runnable
/Callable
引用一个共享的数据结构,是从其他无障碍Runnable
/Callable
小号情况下(可能是由不同的线程simulataneously处理),那么它是你的责任,以确保在这个数据结构线程安全。
要回答问题的第二部分,是的,您可以在提交任何任务之前访问ThreadPoolExecutor。例如
BlockingQueue<Runnable> workQ = new LinkedBlockingQueue<Runnable>();
ExecutorService execService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.SECONDS, workQ);
...
execService.submit(new Callable(...));
编辑
基于Brian的评论,以防万一我误解了您的问题:ExecutorService
将多个生产者线程中的任务提交给遗嘱通常是线程安全的(尽管据我所知,接口API中并未明确提及)。任何不提供线程安全性的实现在多线程环境中都是无用的(因为多个生产者/多个消费者是一个相当普遍的范例),而这正是ExecutorService
(以及其余的java.util.concurrent
)设计目的。
对于 ThreadPoolExecutor
答案是肯定的。 ExecutorService
并没有强制要求或以其他方式保证所有的实现是线程安全的,它不能因为它是一个接口。这些类型的合同不在Java接口范围之内。但是,ThreadPoolExecutor
两者都是并且被明确记录为线程安全的。此外,ThreadPoolExecutor
管理它的作业队列是使用java.util.concurrent.BlockingQueue
哪个接口来请求所有实现都是线程安全的。可以安全地假定的任何java.util.concurrent.*
实现BlockingQueue
都是线程安全的。任何非标准的实现都可能不会,尽管如果有人要提供一个BlockingQueue
不是线程安全的实现队列,那将是完全愚蠢的。
所以标题问题的答案很明显 是肯定的。下一个问题的答案可能是,因为两者之间存在一些差异。
List.hashCode()
)一样。javadocs说“ BlockingQueue实现是线程安全的”(因此,非线程安全的BlockingQueue不仅愚蠢而且有漏洞),但是对于ThreadPoolExecutor或它实现的任何接口都没有这样的文档。
ThreadPoolExecutor
线程安全的文档?
对于ThreadPoolExecutor,它提交是线程安全的。您可以在jdk8中查看源代码。添加新任务时,它使用mainLock来确保线程安全。
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
boolean workerStarted = false;
boolean workerAdded = false;
Worker w = null;
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
与卢克·乌瑟伍德(Luke Usherwood)的回答相反,该文档并未暗含ExecutorService
保证实现是线程安全的。至于ThreadPoolExecutor
具体问题,请参见其他答案。
是的,已指定了事前发生的关系,但这并不意味着有关方法本身的线程安全性,正如Miles所评论的那样。在卢克·厄舍伍德()的回答中,指出前者足以证明后者,但没有提出实际论据。
“线程安全”可能意味着各种各样的事情,但这是一个简单的反例Executor
(没有,ExecutorService
但没有区别),它微不足道地满足了所需的先发生后关系,但是由于对count
字段的不同步访问而不是线程安全的。
class CountingDirectExecutor implements Executor {
private int count = 0;
public int getExecutedTaskCount() {
return count;
}
public void execute(Runnable command) {
command.run();
}
}
免责声明:我不是专家,我找到了这个问题,因为我自己在寻找答案。