Is ExecutorService (specifically ThreadPoolExecutor) thread safe?
Asked Answered
S

6

87

Does the ExecutorService guarantee thread safety ?

I'll be submitting jobs from different threads to the same ThreadPoolExecutor, do I have to synchronize access to the executor before interacting/submitting tasks?

Subjectivism answered 9/11, 2009 at 17:10 Comment(0)
P
31

It's true, the JDK classes in question don't seem to make an explicit guarantee of thread-safe task submission. However, in practice, all ExecutorService implementations in the library are indeed thread-safe in this way. I think it's reasonable to depend on this. Since all the code implementing these features was placed in the public domain, there's absolutely no motivation for anyone to completely rewrite it a different way.

Peroration answered 9/11, 2009 at 21:41 Comment(5)
"placed in the public domain" really? I thought it uses the GPL.Bookbinding
The JDK does, but Doug Lea doesn't.Peroration
There is a sufficient guarantee made about thread-safe task submission: see the bottom of the javadoc for interface ExecutorService, which ThreadPoolExecutor must also adhere to. (More detail in my recently-updated answer.)Uncaredfor
If you have an executor with one thread, and in that thread, you want to submit work to that executor, wait for it to complete, there will be a deadlock issue where the submitted work won't ever get to run. With a synchronized block you the lock would be removed when you enter wait mode. Consider the case where someone is waiting for your task to complete, and in that task, you might scheduele more work to be done based on some criteria. You would then have to wait for them to complete to signal to the original caller when work is actually done.Thatch
This is true for any size executor.Thatch
U
61

(Contrary to other answers) the thread-safety contract is documented: look in the interface javadocs (as opposed to javadoc of methods). For example, at the bottom of the ExecutorService javadoc you find:

Memory consistency effects: Actions in a thread prior to the submission of a Runnable or Callable task to an ExecutorService happen-before any actions taken by that task, which in turn happen-before the result is retrieved via Future.get().

This is sufficient to answer this:

"do I have to synchronize access to the executor before interacting/submitting tasks?"

No you don't. It is fine to construct and submit jobs to any (correctly implemented) ExecutorService without external synchronisation. This is one of the main design goals.

ExecutorService is a concurrent utility, which is to say that it is designed to operate to the greatest extent without requiring synchronisation, for performance. (Synchronisation causes thread-contention, which can degrade multi-threading efficiency - particularly when scaling up to a large number of threads.)

There is no guarantee about at what time in the future the tasks will execute or complete (some may even execute immediately on same thread that submitted them) however the worker thread is guaranteed to have seen all effects that the submitting thread has performed up to the point of submission. Therefore (the thread that runs) your task can also safely read any data created for its use without synchronisation, thread-safe classes or any other forms of "safe publication". The act of submitting the task is itself sufficient for "safe publication" of the input data to the task. You just need to ensure that the input data won't be modified in any way while the task is running.

Similarly, when you fetch the result of the task back via Future.get(), the retrieving thread will be guaranteed to see all effects made by the executor's worker thread (in both the returned result, plus any side-effect changes the worker-thread may have made).

This contract also implies that it is fine for the tasks themselves to submit more tasks.

"Does the ExecutorService guarantee thread safety ?"

Now this part of the question is much more general. For example could not find any statement of a thread-safety contract about the method shutdownAndAwaitTermination - although I note that the code sample in the Javadoc does not use synchronisation. (Although perhaps there's a hidden assumption that the shutdown is instigated by the same thread that created the Executor, and not for example a worker thread?)

BTW I'd recommend the book "Java Concurrency In Practice" for a good grounder on the world of concurrent programming.

Uncaredfor answered 29/9, 2011 at 11:0 Comment(2)
Those aren't full thread-safety guarantees; they only establish a visibility order in specific circumstances. For instance, there is no explicitly documented guarantee that it is safe to call execute() from multiple threads (outside the context of tasks running on the executor).Nikolai
@Nikolai After a couple years more experience :-) ... I disagree. The happens-before relationship is a fundamental concept in the (ground-breaking) Java Memory Model introduced in Java 5, which in turn forms the basic building-block for defining concurrent (as opposed to synchronised) thread-safety contracts. (And I again support my original answer, although hopefully it's clearer now with some editing.)Uncaredfor
P
31

It's true, the JDK classes in question don't seem to make an explicit guarantee of thread-safe task submission. However, in practice, all ExecutorService implementations in the library are indeed thread-safe in this way. I think it's reasonable to depend on this. Since all the code implementing these features was placed in the public domain, there's absolutely no motivation for anyone to completely rewrite it a different way.

Peroration answered 9/11, 2009 at 21:41 Comment(5)
"placed in the public domain" really? I thought it uses the GPL.Bookbinding
The JDK does, but Doug Lea doesn't.Peroration
There is a sufficient guarantee made about thread-safe task submission: see the bottom of the javadoc for interface ExecutorService, which ThreadPoolExecutor must also adhere to. (More detail in my recently-updated answer.)Uncaredfor
If you have an executor with one thread, and in that thread, you want to submit work to that executor, wait for it to complete, there will be a deadlock issue where the submitted work won't ever get to run. With a synchronized block you the lock would be removed when you enter wait mode. Consider the case where someone is waiting for your task to complete, and in that task, you might scheduele more work to be done based on some criteria. You would then have to wait for them to complete to signal to the original caller when work is actually done.Thatch
This is true for any size executor.Thatch
V
9

Your question is rather open-ended: All the ExecutorService interface does is guarantee that some thread somewhere will process the submitted Runnable or Callable instance.

If the submitted Runnable / Callable references a shared data structure that is accessible from other Runnable / Callables instances (potentially being processed simulataneously by different threads), then it is your responsibility to ensure thread safety across this data structure.

To answer the second part of your question, yes you will have access to the ThreadPoolExecutor before submitting any tasks; e.g.

BlockingQueue<Runnable> workQ = new LinkedBlockingQueue<Runnable>();
ExecutorService execService = new ThreadPoolExecutor(4, 4, 0L, TimeUnit.SECONDS, workQ);
...
execService.submit(new Callable(...));

EDIT

Based on Brian's comment and in case I've misunderstood your question: Submission of tasks from multiple producer threads to the ExecutorService will typically be thread-safe (despite not being mentioned explicitly in the interface's API as far as I can tell). Any implementation that didn't offer thread safety would be useless in a multi-threaded environment (as multiple producers / multiple consumers is a fairly common paradigm), and this is specifically what ExecutorService (and the rest of java.util.concurrent) was designed for.

Votaw answered 9/11, 2009 at 17:13 Comment(3)
Isn't what he's asking is that the submission is thread-safe ? i.e. that he can submit from different threadsUncork
Yes, Im asking wether it's safe to submit tasks to the same ThreadPoolExecutor instance from multiple threads. Updated the question since an important "synchronize" word disappeared :|Subjectivism
"Any implementation that didn't offer thread safety would be useless in a multi-threaded environment": it's not completely implausible for a hypothetical ExecutorService to provide a non-thread-safe implementation, as single-producer is a pretty common pattern. (But for ThreadPoolExecutor, intended for general use, that comment certainly stands)Nikolai
S
6

For ThreadPoolExecutor the answer is simply yes. ExecutorService does not mandate or otherwise guarantee that all implementations are thread-safe, and it cannot as it is an interface. These types of contracts are outside of the scope of a Java interface. However, ThreadPoolExecutor both is and is clearly documented as being thread-safe. Moreover, ThreadPoolExecutor manages it's job queue using java.util.concurrent.BlockingQueue which is an interface that requests all implementations are thread-safe. Any java.util.concurrent.* implementation of BlockingQueue can be safely assumed to be thread-safe. Any non-standard implementation may not, although that would be outright silly if someone were to provide a BlockingQueue implementing queue which was not thread-safe.

So the answer to your title question is clearly yes. The answer to the subsequent body of your question is probably, as there are some discrepancies between the two.

Sabinesabino answered 1/7, 2010 at 17:31 Comment(2)
Interfaces can and do mandate thread-safe implementations. Thread-safety is a documented contract, just like any other type of behavior (such as List.hashCode()). The javadocs say "BlockingQueue implementations are thread-safe" (so a non-thread-safe BlockingQueue is not just silly but buggy), but there is no such documentation for ThreadPoolExecutor or any of the interfaces it implements.Nikolai
Can you please reference the documentation that clearly states that ThreadPoolExecutor is thread-safe?Quintal
T
4

For ThreadPoolExecutor, it submit is thread safe. You can see the source code in jdk8. When adding a new task, it uses a mainLock to ensure the thread safe.

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;
        }
Tiltyard answered 26/3, 2018 at 7:55 Comment(0)
S
3

Contrary to what the answer by Luke Usherwood claims, it is not implied by the documentation that ExecutorService implementations are guaranteed to be thread-safe. As to the question of ThreadPoolExecutor specifically, see other answers.

Yes, a happens-before relationship is specified, but this does not imply anything about the thread-safety of the methods themselves, as commented by Miles. In Luke Usherwood's answer it is stated that the former is sufficient to prove the latter but no actual argument is made.

"Thread-safety" can mean various things, but here is a simple counter-example of an Executor (not ExecutorService but it makes no difference) that trivially meets the required happens-before relationship but is not thread-safe because of unsynchronized access to the count field.

class CountingDirectExecutor implements Executor {

    private int count = 0;

    public int getExecutedTaskCount() {
        return count;
    }

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

Disclaimer: I'm no expert and I found this question because I was searching for the answer myself.

Saturate answered 21/8, 2015 at 19:38 Comment(2)
What you state is all true, but the question specifically asks "do I have to synchronize access to the executor" - so I read "thread safety" in this context to be only talking about the thread-safety of (the state / data inside the) executor, and the actions of invoking its methods.Uncaredfor
How to make submitted tasks themselves have "thread safe side effects" is a much bigger topic! (It's much easier all around if they don't. Like, if some immutable computed result can just be passed back. When they do touch mutable shared state, then sure: you need to take care to define & understand the thread boundaries & consider thread-safety, dead-locks, live-locks etc.)Uncaredfor

© 2022 - 2024 — McMap. All rights reserved.