I have a fixed thread pool ExecutorService of width 10, and a list of 100 Callable‘s, each waiting for 20 seconds and recording their interrupts.
I’m calling invokeAll on that list in a separate thread, and almost immediately interrupting this thread. ExecutorService execution is interrupted as expected, but the actual number of interrupts recorded by Callables is far more than expected 10 – around 20-40. Why is that so, if ExecutorService can execute no more than 10 threads simultaneously?
Full source: (You may need to run it more that once due to concurrency)
@Test
public void interrupt3() throws Exception{
int callableNum = 100;
int executorThreadNum = 10;
final AtomicInteger interruptCounter = new AtomicInteger(0);
final ExecutorService executorService = Executors.newFixedThreadPool(executorThreadNum);
final List <Callable <Object>> executeds = new ArrayList <Callable <Object>>();
for (int i = 0; i < callableNum; ++i) {
executeds.add(new Waiter(interruptCounter));
}
Thread watcher = new Thread(new Runnable() {
@Override
public void run(){
try {
executorService.invokeAll(executeds);
} catch(InterruptedException ex) {
// NOOP
}
}
});
watcher.start();
Thread.sleep(200);
watcher.interrupt();
Thread.sleep(200);
assertEquals(10, interruptCounter.get());
}
// This class just waits for 20 seconds, recording it's interrupts
private class Waiter implements Callable <Object> {
private AtomicInteger interruptCounter;
public Waiter(AtomicInteger interruptCounter){
this.interruptCounter = interruptCounter;
}
@Override
public Object call() throws Exception{
try {
Thread.sleep(20000);
} catch(InterruptedException ex) {
interruptCounter.getAndIncrement();
}
return null;
}
}
Using WinXP 32-bit, Oracle JRE 1.6.0_27 and JUnit4
I disagree with the hypothesis that you should only receive 10 interrupts.
Essentially, my argument is that there is no guarantee that the Watcher thread will be allowed to cancel all 100 “Waiter” RunnableFuture instances before it has to yield the time slice and allow the ExecutorService’s worker threads to start more Waiter tasks.
Update: Showing code from
AbstractExecutorServiceThe finally block which contains
f.cancel(true)is when the interrupt would be propagated to task which is currently running. As you can see, this is a tight loop, but there is no guarantee that the thread executing the loop would be able to iterate through all instances ofFuturein one time slice.