ExecutorService
是否保证线程安全?
我将从不同的线程向同一个ThreadPoolExecutor提交作业,在交互/提交任务之前是否必须同步对执行程序的访问?
答案 0 :(得分:46)
(与其他答案相反)线程安全合同 记录:查看interface
javadocs(而不是方法的javadoc)。例如,在ExecutorService javadoc的底部,您会找到:
内存一致性效果:在之前的一个线程中的操作 向RuncutorService提交Runnable或Callable任务 发生在之前该任务采取的任何行动,反过来 发生 - 之前通过Future.get()检索结果。
这足以回答这个问题:
“在交互/提交任务之前,我是否必须同步对执行程序的访问?”
不,不。在没有外部同步的情况下构造和提交作业(正确实现)ExecutorService
是很好的。这是主要的设计目标之一。
ExecutorService
是一个并发实用程序,也就是说它旨在最大程度地运行而不需要同步,以提高性能。 (同步会导致线程争用,这会降低多线程效率 - 尤其是在扩展到大量线程时。)
无法保证将来在什么时间执行或完成任务(有些甚至可以在提交它们的同一线程上立即执行)但是工作线程保证已经看到提交线程执行的所有效果直至提交。因此(运行的线程)您的任务也可以安全地读取为其使用而创建的任何数据,而无需同步,线程安全类或任何其他形式的“安全发布”。提交任务的行为本身足以“安全发布”任务的输入数据。您只需确保在任务运行时不会以任何方式修改输入数据。
类似地,当您通过Future.get()
获取任务结果时,检索线程将保证看到执行程序的工作线程所做的所有效果(在返回的结果中,加上任何副作用更改)工人线程可能已经做过。)
此合同还意味着任务本身可以提交更多任务。
“ExecutorService是否保证线程安全?”
现在这部分问题更为笼统。例如,找不到关于方法shutdownAndAwaitTermination
的线程安全合同的任何声明 - 尽管我注意到Javadoc中的代码示例不使用同步。 (虽然可能有一个隐藏的假设,即关闭是由创建Executor的同一个线程发起的,而不是例如工作线程?)
BTW我推荐“Java Concurrency In Practice”这本书,以便在并发编程领域找到一个好的地方。
答案 1 :(得分:31)
确实,有问题的JDK类似乎没有明确保证线程安全的任务提交。但是,实际上,库中的所有ExecutorService实现都是以这种方式确实是线程安全的。我认为依靠这个是合理的。由于实现这些功能的所有代码都放在公共领域,因此绝对没有动机让任何人以不同的方式完全重写它。
答案 2 :(得分:9)
您的问题是开放式的:所有ExecutorService
接口都确保某个线程可以处理提交的Runnable
或Callable
实例。
如果提交的Runnable
/ Callable
引用了可从其他Runnable
/ Callable
实例访问的共享数据结构(可能由不同的线程同时处理),那么确保整个数据结构的线程安全是你的责任。
要回答问题的第二部分,是的,您可以在提交任何任务之前访问ThreadPoolExecutor; e.g。
BlockingQueue<Runnable> workQ = new LinkedBlockingQueue<Runnable>();
ExecutorService execService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.SECONDS, workQ);
...
execService.submit(new Callable(...));
修改强>
根据Brian的评论,如果我误解了你的问题:从多个生产者线程向ExecutorService
提交任务通常是线程安全的(尽管未在接口的API中明确提及,我可以告诉)。任何不提供线程安全性的实现在多线程环境中都是无用的(因为多个生产者/多个消费者是一个相当普遍的范例),这特别是ExecutorService
(以及{{1}的其余部分}})是为。
答案 3 :(得分:6)
对于ThreadPoolExecutor
,答案只是是。 ExecutorService
不授权或以其他方式保证所有实现都是线程安全的,并且它不能作为接口。这些类型的契约超出了Java接口的范围。但是,ThreadPoolExecutor
和ThreadPoolExecutor
都清楚地记录为线程安全的。此外,java.util.concurrent.BlockingQueue
使用java.util.concurrent.*
来管理它的作业队列,BlockingQueue
是一个请求所有实现都是线程安全的接口。 BlockingQueue
的任何{{1}}实现都可以安全地假设为线程安全的。任何非标准的实现都可能没有,尽管如果有人要提供一个不是线程安全的{{1}}实现队列,那将是完全愚蠢的。
所以你的标题问题的答案显然是是。您问题的后续主体的答案可能是 ,因为两者之间存在一些差异。
答案 4 :(得分:1)
与Luke Usherwood声称的答案相反,文档并未暗示ExecutorService
实现保证是线程安全的。至于ThreadPoolExecutor
的具体问题,请参阅其他答案。
是的,发生在指定关系之前,但这并不意味着方法本身的线程安全性,如Miles所评论。在Luke Usherwood的答案中,陈述前者足以证明后者,但没有做出实际论证。
“线程安全”可能意味着各种各样的事情,但这里有一个简单的例子Executor
(不是ExecutorService
但没有区别),它可以轻松地满足所需的发生-before 关系,但由于对count
字段的不同步访问,因此不是线程安全的。
class CountingDirectExecutor implements Executor {
private int count = 0;
public int getExecutedTaskCount() {
return count;
}
public void execute(Runnable command) {
command.run();
}
}
免责声明:我不是专家,我发现了这个问题,因为我自己在寻找答案。
答案 5 :(得分:1)
对于ThreadPoolExecutor,它提交是线程安全的。您可以在jdk8中看到源代码。添加新任务时,它使用mainLock来确保线程安全。
{{1}}