ExecutorService(特别是ThreadPoolExecutor)线程安全吗?

lee*_*roy 75 java multithreading executorservice

ExecutorService保证是否安全?

我将从不同的线程向同一个ThreadPoolExecutor提交作业,在交互/提交任务之前是否必须同步对执行程序的访问?

Luk*_*ood 51

(相反,其他的答案)线程安全性合同记载:看在interfaceJavadoc中(而不是方法的javadoc).例如,在ExecutorService javadoc 的底部,您会发现:

内存一致性效果:在向ExecutorService提交Runnable或Callable任务之前的线程中的操作发生在该任务执行 任何操作之前,而该操作又 发生在通过Future.get()检索结果之前.

这足以回答这个问题:

"在交互/提交任务之前,我是否必须同步对执行程序的访问?"

不,你没有.在ExecutorService没有外部同步的情况下构造和提交作业(正确实现)是可以的.这是主要的设计目标之一.

ExecutorService是一个并发实用程序,也就是说它被设计为在不需要同步的情况下最大程度地运行以提高性能.(同步会导致线程争用,这会降低多线程效率 - 尤其是在扩展到大量线程时.)

没有关于在未来什么时候任务将执行或完成担保(有的甚至会立即执行上提交他们相同的线程),然而工作线程是保证已经看到,提交线程执行的所有效果达提交的意见.因此(运行的线程)您的任务也可以安全地读取为其使用而创建的任何数据,而无需同步,线程安全类或任何其他形式的"安全发布".提交任务的行为本身足以"安全发布"任务的输入数据.您只需确保在任务运行时不会以任何方式修改输入数据.

类似地,当您通过提取任务的结果时Future.get(),检索线程将保证看到执行程序的工作线程所做的所有效果(在返回的结果中,以及工作线程可能已经做出的任何副作用更改) .

该合同还意味着任务本身可以提交更多任务.

"ExecutorService是否保证线程安全?"

现在这部分问题更为笼统.例如,找不到关于该方法的线程安全合同的任何声明 shutdownAndAwaitTermination- 尽管我注意到Javadoc中的代码示例不使用同步.(虽然可能有一个隐藏的假设,即关闭是由创建Executor的同一个线程发起的,而不是例如工作线程?)

顺便说一下,我推荐"Java Concurrency In Practice"一书,以便在并发编程领域找到一个好的地方.

  • 这些不是完整的线程安全保证; 他们只在特定情况下建立可见性顺序.例如,没有明确记录保证从多个线程(在执行程序上运行的任务的上下文之外)调用execute()是安全的. (3认同)
  • @Miles 经过几年的经验:-) ...我不同意。发生之前关系是 Java 5 中引入的(开创性的)Java 内存模型中的一个基本概念,它反过来又形成了定义并发(而不是同步)线程安全契约的基本构建块。(我再次支持我原来的答案,尽管希望通过一些编辑现在可以更清楚。) (2认同)

Kev*_*ion 30

确实,有问题的JDK类似乎没有明确保证线程安全的任务提交.但是,实际上,库中的所有ExecutorService实现都是以这种方式确实是线程安全的.我认为依靠这个是合理的.由于实现这些功能的所有代码都放在公共领域,因此绝对没有动机让任何人以不同的方式完全重写它.

  • 有关线程安全任务提交的充分保证:请参阅javadoc底部的`interface ExecutorService`,`ThreadPoolExecutor`也必须遵守.(我最近更新的答案中有更详细的说明.) (7认同)
  • 每个人都会投票支持卢克的答案.:-) (6认同)

Ada*_*ski 9

你的问题是开放式的:所有ExecutorService界面都确保某个线程可以处理提交的RunnableCallable实例.

如果提交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(...));
Run Code Online (Sandbox Code Playgroud)

编辑

根据Brian的评论,如果我误解了你的问题:从多个生产者线程向ExecutorService遗嘱提交任务通常是线程安全的(尽管我没有在接口的API中明确提到).任何不提供线程安全性的实现在多线程环境中都是无用的(因为多个生产者/多个消费者是一个相当普遍的范例),这是ExecutorService(和其余的java.util.concurrent)设计的具体内容.

  • 他提出的问题不是*提交*是线程安全的吗?即他可以从不同的线程提交 (8认同)
  • “任何不提供线程安全的实现在多线程环境中都是无用的”:假设的 ExecutorService 提供非线程安全的实现并非完全不可能,因为单生产者是一种非常常见的模式。(但是对于用于一般用途的 ThreadPoolExecutor,该评论当然成立) (2认同)

Sco*_*Coy 6

对于ThreadPoolExecutor简单的答案是肯定的. ExecutorService没有强制要求或以其他方式保证所有的实现是线程安全的,它不能因为它是一个接口.这些类型的契约超出了Java接口的范围.但是,ThreadPoolExecutor两者都是并且明确记录为线程安全的.此外,ThreadPoolExecutor管理它的作业队列使用java.util.concurrent.BlockingQueue哪个接口请求所有实现都是线程安全的.可以安全地假设任何java.util.concurrent.*实现BlockingQueue都是线程安全的.任何非标准的实现都可能没有,尽管如果有人要提供一个BlockingQueue非线程安全的实现队列,那将是完全愚蠢的.

所以你的标题问题的答案显然是肯定的.您问题的后续主体的答案可能是,因为两者之间存在一些差异.

  • 接口可以并且确实需要线程安全的实现.线程安全是一种文档化的契约,就像任何其他类型的行为一样(例如`List.hashCode()`).javadocs说"BlockingQueue实现是线程安全的"(因此非线程安全的BlockingQueue不仅仅是愚蠢而且有bug),但ThreadPoolExecutor或它实现的任何接口都没有这样的文档. (4认同)