Reputation: 31
I found an unexpected deadlock while running tasks in a ThreadPoolExecutor.
The idea is a main task that launches a secondary task that changes a flag. The main task halts until the secondary task updates the flag.
I'd like to know:
Is corePoolSize =2 a safe value to prevent this kind of deadlocks?
import java.util.concurrent.*;
class ExecutorDeadlock {
/*------ FIELDS -------------*/
boolean halted = true;
ExecutorService executor;
Runnable secondaryTask = new Runnable() {
public void run() {
System.out.println("secondaryTask started");
halted = false;
System.out.println("secondaryTask completed");
}
};
Runnable primaryTask = new Runnable() {
public void run() {
System.out.println("primaryTask started");
executor.execute(secondaryTask);
while (halted) {
try {
Thread.sleep(500);
}
catch (Throwable e) {
e.printStackTrace();
}
}
System.out.println("primaryTask completed");
}
};
/*-------- EXECUTE -----------*/
void execute(){
executor.execute(primaryTask);
}
/*-------- CTOR -----------*/
ExecutorDeadlock(int corePoolSize,BlockingQueue<Runnable> workQueue) {
this.executor = new ThreadPoolExecutor(corePoolSize, 4,0L, TimeUnit.MILLISECONDS, workQueue);
}
/*-------- TEST -----------*/
public static void main(String[] args) {
new ExecutorDeadlock(2,new LinkedBlockingQueue<>()).execute();
//new ExecutorDeadlock(1,new LinkedBlockingQueue<>()).execute();
//new ExecutorDeadlock(0,new SynchronousQueue<>()).execute();
}
}
Upvotes: 2
Views: 1747
Reputation: 32507
How do you expect for this to work on threads count <2 if
Tasks are fetched from the queue by executor service when there are free executors in pool. In you case (<2) executor thread is never released by first task. There is no deadlock issue here.
EDIT:
Ok, I'v dug up some info and this is what I have found out. First of all some info from ThreadPoolExecutor
Any BlockingQueue may be used to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:
If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing. If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread. If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.
Ok and now as for queuess offer
methods
SyncQueue:
Inserts the specified element into this queue, if another thread is waiting to receive it.
LinkedBlockingQueue
Inserts the specified element into this queue, waiting if necessary for space to become available.
return value of offer
method determines whetever new task will be queued or ran in new thread.
As LinkedBlockingQueue
enqueues new task because it can as there is enought capacity, task is enqueued and no new threads are spawn. However SyncQueu
will not enqueue another task, as there is no other threads that are waiting for something to be enqueued (offer
returns false as task is not enqueued) and thats why new executor thread will be spawned.
If you read javadocs for ThreadPoolExecutor
LinkedBlockingQueue
and SynchronousQueue
+ check implementation of execute
method, you will get to the same conclusion.
So you were wrong, there is explenation in documentation :)
Upvotes: 6