繁体   English   中英

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

[英]Is ExecutorService (specifically ThreadPoolExecutor) thread safe?

ExecutorService是否保证线程安全?

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

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

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

这足以回答这个问题:

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

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

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

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

类似地,当你通过Future.get()获取任务的结果时,检索线程将保证看到执行者的工作线程所做的所有效果(在返回的结果中,加上任何副作用都会改变工作者 -线程可能已经制作)。

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

“ExecutorService是否保证线程安全?”

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

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

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

您的问题是开放式的:所有ExecutorService接口都确保某个线程可以处理提交的RunnableCallable实例。

如果提交的Runnable / Callable引用了可从其他Runnable / Callable实例访问的共享数据结构(可能由不同的线程同时处理),则您有责任确保跨此数据结构的线程安全。

要回答问题的第二部分,是的,在提交任何任务之前,您将可以访问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管理它的作业队列,这是一个请求所有实现都是线程安全的接口。 可以安全地假设BlockingQueue任何java.util.concurrent.*实现都是线程安全的。 任何非标准的实现都可能没有,尽管如果有人提供一个非线程安全的BlockingQueue实现队列,那将是完全愚蠢的。

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

Luke Usherwood所声称的答案相反,文档并未暗示ExecutorService实现保证是线程安全的。 至于ThreadPoolExecutor具体问题,请参阅其他答案。

是的,指定了之前发生的关系,但这并不意味着任何关于方法本身的线程安全性的事情,正如Miles所评论的那样。 Luke Usherwood的回答中指出,前者足以证明后者,但没有做出实际的论证。

“线程安全”可能意味着各种各样的东西,但这里有一个简单的反例例,它是一个Executor (不是ExecutorService但没有区别),它通常满足所需的先发生关系,但由于不同步的访问权限而不是线程安全的。 count字段。

class CountingDirectExecutor implements Executor {

    private int count = 0;

    public int getExecutedTaskCount() {
        return count;
    }

    public void execute(Runnable command) {
        command.run();
    }
}

免责声明:我不是专家,我发现了这个问题,因为我自己正在寻找答案。

对于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;
        }

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM