In this simple short program, you will notice that the program hangs forever because the take() does not release the thread. According to my understanding, take() causes the thread to be released even though the task itself is blocked on take().
Edited:
This works (thanks to you all for fixing the autoboxing):
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducersConsumers {
private static int THREAD_COUNT = 5;
public static void main(String[] args) throws ExecutionException, InterruptedException {
final ExecutorService executorPool = Executors.newFixedThreadPool(THREAD_COUNT);
final LinkedBlockingQueue<Long> queue = new LinkedBlockingQueue<Long>();
Collection<Future<Long>> collection = new ArrayList<Future<Long>>();
// producer:
for (int i = 0; i < 20; i++) {
collection.add(executorPool.submit(new Callable<Long>() {
#Override
public Long call() throws Exception {
for (int i = 100; i >= 0; i--) {
queue.put((long) i);
}
return -1L;
}
}));
}
// consumer:
for (int i = 0; i < 20; i++) {
collection.add(executorPool.submit(new Callable<Long>() {
#Override
public Long call() throws Exception {
while (true) {
Long item = queue.take();
if (item.intValue() == 0) {
break;
}
}
return 1L;
}
}));
}
long sum = 0;
for (Future<Long> item : collection) {
sum += item.get();
}
executorPool.shutdown();
System.out.println("sum = " + sum);
}
}
But if you swap the producer and consumer invocations, it will hang:
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
public class ProducersConsumers {
private static int THREAD_COUNT = 5;
public static void main(String[] args) throws ExecutionException, InterruptedException {
final ExecutorService executorPool = Executors.newFixedThreadPool(THREAD_COUNT);
final LinkedBlockingQueue<Long> queue = new LinkedBlockingQueue<Long>();
Collection<Future<Long>> collection = new ArrayList<Future<Long>>();
// consumer:
for (int i = 0; i < 20; i++) {
collection.add(executorPool.submit(new Callable<Long>() {
#Override
public Long call() throws Exception {
while (true) {
Long item = queue.take();
if (item.intValue() == 0) {
break;
}
}
return 1L;
}
}));
}
// producer:
for (int i = 0; i < 20; i++) {
collection.add(executorPool.submit(new Callable<Long>() {
#Override
public Long call() throws Exception {
for (int i = 100; i >= 0; i--) {
queue.put((long) i);
}
return -1L;
}
}));
}
long sum = 0;
for (Future<Long> item : collection) {
sum += item.get();
}
executorPool.shutdown();
System.out.println("sum = " + sum);
}
}
To my understanding the producer and consumer order should not matter. In other words, there is a notion of task and thread. Thread are independent of code program whereas task is associated with a certain program. Therefore, in my example, when the JVM assigns a thread to execute of the Callable tasks, if the consumer is instantiated first, then the task will block on take(). Once the JVM discovers that the task is blocked, it will release the thread (or as I understand it but it is not releasing it) and places it back to the worker thread pool in preparation for processing a runnable task (which in this case are the Producers). Consequently, at the end of instantiating all the Callable's, there should be 40 tasks but only 5 threads; 20 of those tasks are blocked, 5 of the tasks should be running and 15 should be waiting (to run).
I think you misunderstand how threads and threadpools work. A threadpool typically has a work item queue which contains items to be worked on (in your case Callable<>s).
It also contains a (maximum) number of threads (in your case 5) which can work on those items.
The lifetime of an active thread is defined by the code it executes - usually a method. The thread becomes "alive" when it starts executing the method and it ends when it returns. If the method blocks to wait on some signal it does not mean the the thread can go away and execute some other method - that's not how threads work. Instead the thread will be blocked until it can continue execution and enable other threads to be run.
The method which is run by a threadpool thread usually looks like this:
void threadloop()
{
while (!quit)
{
Callable<T> item = null;
synchronized (workQueue)
{
if (workQueue.Count == 0)
workQueue.wait();
// we could have been woken up for some other reason so check again
if (workQueue.Count > 0)
item = workQueue.pop();
}
if (item != null)
item.Call();
}
}
This is more or less pseudo code (I'm not a Java developer) but it should show the concept. Now item.Call() executes the method which is supplied by the user of the pool. If that method blocks, then what happens? Well - the thread will be blocked in its execution of item.Call() until the method wakes up again. It can't just go away and execute some other code arbitrarily.
From javadoc:
Retrieves and removes the head of this queue, waiting if no elements are present on this queue.
It will wait: you're running in main, so it will stay there.
EDIT: correction: the blocking still happens (in the thread pool threads, not in main). There is no yielding going on: the 20 threads are blocked on the take calls, so no put calls execute, so the Futures never complete, so the program hangs.
I don't know what exactly you mean by release thread but once you block on take() the calling thread is blocked and is not going back to the pool.
I think you've misunderstood what gets "blocked" in a BlockingQueue.
The call to queue.take() blocks the thread that invoked it until something is available in the queue. This means that the thread will wait there endlessly, unless interrupted, until an item is added to the queue.
The second code sample hangs the problem because you are adding 20 tasks to wait for an item to appear in the BlockingQueue, and the executor has just 5 threads in it - thus the first five tasks cause all five of the threads to block. This executor is filled with 15 further consumer tasks.
The addition of tasks in the second for-loop to add items to the queue results in 20 tasks that can never be executed, because all threads in the executor are stuck waiting.
So when you say this:
According to my understanding, take() causes the thread to be released even though the task itself is blocked on take().
You have a misunderstanding because there is no difference here between what the "thread" does and what the "task" does. A thread cannot be "released" while the task is blocked - it is the thread that runs the task. When the thread encounters a blocking call to take(), the thread is blocked, period.
Related
I'm trying to simulate a non-thread safe counter class by incrementing the count in an executor service task and using countdown latches to wait for all threads to start and then stop before reading the value in the main thread.
The issue is that when I run it the System.out at the end always returns 10 as the correct count value. I was expecting to see some other value when I run this as the 10 threads may see different values.
My code is below. Any idea what is happening here? I'm running it in Java 17 and from Intellij IDEA.
Counter.java
public class Counter {
private int counter = 0;
public void incrementCounter() {
counter += 1;
}
public int getCounter() {
return counter;
}
}
Main.java
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch startSignal = new CountDownLatch(10);
CountDownLatch doneSignal = new CountDownLatch(10);
Counter counter = new Counter();
for (int i=0; i<10; i++) {
executorService.submit(() -> {
try {
startSignal.countDown();
startSignal.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
counter.incrementCounter();
doneSignal.countDown();
});
}
doneSignal.await();
System.out.println("Finished: " + counter.getCounter());
executorService.shutdownNow();
}
}
It's worth remembering that just because something isn't synchronised correctly, it could still perform correctly under some circumstances, it just isn't guaranteed to do so in every situation, on every JVM, on every hardware.
In other words, there is no reverse guarantee, optimisers for example are free to decide your code can be replaced at little to no cost with a correctly synchronised implementation.
(Whether that is what's actually happening here isn't obvious to me at first glance.)
I've got the folowing code:
List<String> instances2 = Arrays.asList("instances/umps20.txt","instances/umps22.txt","instances/umps24.txt","instances/umps26.txt","instances/umps28.txt","instances/umps30.txt","instances/umps32.txt");
List<Integer> qq1 = Arrays.asList(9,10,11,12,13,14,14);
List<Integer> qq2 = Arrays.asList(4,4,5,5,5,5,6);
for (int i = 0; i<7; i++) {
Tournament t = p.process(instances2.get(i));
int nTeams = t.getNTeams();
int q1 = qq1.get(i);
int q2 = qq2.get(i);
UndirectedGraph graph = g.create(t, q1, q2);
new Choco(graph, nTeams);
}
}
Now i want to put a limit on each iteration. So after let's say 3h = 10 800 000ms, i would like for everything in the for-loop to stop and start the next iteration over the loop. Any ideas?
Thanks in advance!
Nicholas
You can get the System time before you start the loop and compare it after each iteration to check if the time is over the specified time, like this:
On for loop starting:
long start = System.currentTimeMillis();
in each iteration:
if(start + 10_800_000 >= System.currentTimeMillis()){
start = System.currentTimeMillis();
i++;
}
and you have to remove the i++ in the for loop
for (int i = 0; i<7;) {
You will have to create a new thread which will run your loop, the ExecutorService will run this loop (or whatever code you put into the call() method) for the specified amount of time.
Here is a demo of a task which takes 5 seconds to run, it will be interrupted after 3 seconds:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class QuickTest {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(new Task());
try {
System.out.println("Started.."); // your task is running
System.out.println(future.get(3, TimeUnit.SECONDS)); // enter the amount of time you want to allow your code to run
System.out.println("Finished!"); // the task finished within the given time
} catch (TimeoutException e) {
future.cancel(true);
System.out.println("Terminated!"); // the task took too long and was interrupted
}
executor.shutdownNow();
}
}
class Task implements Callable<String> {
#Override
public String call() throws Exception { // enter the code you want to run for x time in here
Thread.sleep(5000); // Just to demo some code which takes 5 seconds to finish.
return "Ready!"; // code finished and was not interrupted (you gave it enough time).
}
}
There are many ways of implementing the requested functionality.
One approach could be converting the code in the for to a FutureTask object and submit it to an ExecutorService - even one with just 1 thread, if the loop has to be executed in sequence - e.g.
ExecutorService executor = Executors.newFixedThreadPool(1);
The benefit of having a FutureTask (or any other object implementing the Future interface), is that the cancel() method can be used to make sure that the interrupted iteration will not create any side effects.
For the interrupts, there are numerous alternatives. For example, the javax.swing.Timer class can be used, which fires ActionEvent notifications after the expiry of the timer.
In the above approach, the task (for loop code) will be executed until completion, or until an ActionEvent is received from the timer. In the latter case, a call to cancel() can be used to stop the running task and the next task will start. The counter of the total number of iterations can be maintained at the same place.
For more sophisticated solutions, one can play with the various implementations of ExecutorService and timeout specification options, as in another StackOverflow question.
I was referring the concept of BlockingQue and I found one example here.
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue; /* j a v a 2s . co m*/
public class Main {
public static void main(String[] argv) throws Exception {
int capacity = 10;
BlockingQueue < Integer > queue = new ArrayBlockingQueue < Integer > (capacity);
int numWorkers = 2;
Worker[] workers = new Worker[numWorkers];
for (int i = 0; i < workers.length; i++) {
workers[i] = new Worker(queue);
workers[i].start();
}
for (int i = 0; i < 10; i++) {
queue.put(i);
}
}
}
class Worker extends Thread {
BlockingQueue < Integer > q;
Worker(BlockingQueue < Integer > q) {
this.q = q;
}
public void run() {
try {
while (true) {
Integer x = q.take();
if (x == null) {
break;
}
System.out.println(x);
}
} catch (InterruptedException e) {}
}
}
In the example, they have used only one thread which is a Worker thread.
What I understood about BlockingQue is that it is an alternate solution to Producer-Consumer pattern.
So we need two threads to work on. Hence I have doubts/questions.
Below are my questions.
have they used main thread as another thread?
When I run the application, the program does not get exited. I did not understand the reason why the main program does not exit?
In the example code that you are referring , you have one Producer (Main Thread) and two Consumers (Worker Threads).
In Producer - Consumer problem, its not necessary to have only one Producer and only one Consumer - you can have multiple producers and multiple consumers. Their relative number is usually decided about who is doing more complex and time consuming tasks.
Answer 1: main thread is producer thread since it is being used to put items to BlockingQueue, queue.put(i)
Answer 2: your main thread exits after putting ten elements to queue but your worker thread keeps waiting for elements ( even after consuming ten elements ) because q.take() is a blocking method i.e. it waits for more elements to be put to queue (when queue is empty)
Solution : You need to put two EOF elements / Objects (END OF FILE) to queue and do a check like you did , if (x == null). Try putting two extra nulls in queue so when your worker/consumer threads find it, they will terminate. Currently, your condition if (x == null) is never met.
Answer 1. No they have not used main thread for anything else. Main thread exit perfectly.
Answer 2. JVM does not exit until all the non-daemon thread ends. (Source: http://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html)
If you want the JVM to exit once the main thread finishes make your Worker threads to daemon thread by adding workers[i].setDaemon(true); inside your for loop.
The reason why your non-daemon threads doesn't exist is you have Integer x = q.take(); inside while loop on Worker's run method. thus those Worker threads are forever waiting for new Integers to be put on the queue.
Suggestions: You can use Eclipse Java IDE to debug and see whats actually going on each thread
This is a java concurrency question. 10 jobs need to be done, each of them will have 32 worker threads. Worker thread will increase a counter . Once the counter is 32, it means this job is done and then clean up counter map. From the console output, I expect that 10 "done" will be output, pool size is 0 and counterThread size is 0.
The issues are :
most of time, "pool size: 0 and countThreadMap size:3" will be
printed out. even those all threads are gone, but 3 jobs are not
finished yet.
some time, I can see nullpointerexception in line 27. I have used ConcurrentHashMap and AtomicLong, why still have concurrency
exception.
Thanks
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicLong;
public class Test {
final ConcurrentHashMap<Long, AtomicLong[]> countThreadMap = new ConcurrentHashMap<Long, AtomicLong[]>();
final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
final ThreadPoolExecutor tPoolExecutor = ((ThreadPoolExecutor) cachedThreadPool);
public void doJob(final Long batchIterationTime) {
for (int i = 0; i < 32; i++) {
Thread workerThread = new Thread(new Runnable() {
#Override
public void run() {
if (countThreadMap.get(batchIterationTime) == null) {
AtomicLong[] atomicThreadCountArr = new AtomicLong[2];
atomicThreadCountArr[0] = new AtomicLong(1);
atomicThreadCountArr[1] = new AtomicLong(System.currentTimeMillis()); //start up time
countThreadMap.put(batchIterationTime, atomicThreadCountArr);
} else {
AtomicLong[] atomicThreadCountArr = countThreadMap.get(batchIterationTime);
atomicThreadCountArr[0].getAndAdd(1);
countThreadMap.put(batchIterationTime, atomicThreadCountArr);
}
if (countThreadMap.get(batchIterationTime)[0].get() == 32) {
System.out.println("done");
countThreadMap.remove(batchIterationTime);
}
}
});
tPoolExecutor.execute(workerThread);
}
}
public void report(){
while(tPoolExecutor.getActiveCount() != 0){
//
}
System.out.println("pool size: "+ tPoolExecutor.getActiveCount() + " and countThreadMap size:"+countThreadMap.size());
}
public static void main(String[] args) throws Exception {
Test test = new Test();
for (int i = 0; i < 10; i++) {
Long batchIterationTime = System.currentTimeMillis();
test.doJob(batchIterationTime);
}
test.report();
System.out.println("All Jobs are done");
}
}
Let’s dig through all the mistakes of thread related programming, one man can make:
Thread workerThread = new Thread(new Runnable() {
…
tPoolExecutor.execute(workerThread);
You create a Thread but don’t start it but submit it to an executor. It’s a historical mistake of the Java API to let Thread implement Runnable for no good reason. Now, every developer should be aware, that there is no reason to treat a Thread as a Runnable. If you don’t want to start a thread manually, don’t create a Thread. Just create the Runnable and pass it to execute or submit.
I want to emphasize the latter as it returns a Future which gives you for free what you are attempting to implement: the information when a task has been finished. It’s even easier when using invokeAll which will submit a bunch of Callables and return when all are done. Since you didn’t tell us anything about your actual task, it’s not clear whether you can let your tasks simply implement Callable (may return null) instead of Runnable.
If you can’t use Callables or don’t want to wait immediately on submission, you have to remember the returned Futures and query them at a later time:
static final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public static List<Future<?>> doJob(final Long batchIterationTime) {
final Random r=new Random();
List<Future<?>> list=new ArrayList<>(32);
for (int i = 0; i < 32; i++) {
Runnable job=new Runnable() {
public void run() {
// pretend to do something
LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(r.nextInt(10)));
}
};
list.add(cachedThreadPool.submit(job));
}
return list;
}
public static void main(String[] args) throws Exception {
Test test = new Test();
Map<Long,List<Future<?>>> map=new HashMap<>();
for (int i = 0; i < 10; i++) {
Long batchIterationTime = System.currentTimeMillis();
while(map.containsKey(batchIterationTime))
batchIterationTime++;
map.put(batchIterationTime,doJob(batchIterationTime));
}
// print some statistics, if you really need
int overAllDone=0, overallPending=0;
for(Map.Entry<Long,List<Future<?>>> e: map.entrySet()) {
int done=0, pending=0;
for(Future<?> f: e.getValue()) {
if(f.isDone()) done++;
else pending++;
}
System.out.println(e.getKey()+"\t"+done+" done, "+pending+" pending");
overAllDone+=done;
overallPending+=pending;
}
System.out.println("Total\t"+overAllDone+" done, "+overallPending+" pending");
// wait for the completion of all jobs
for(List<Future<?>> l: map.values())
for(Future<?> f: l)
f.get();
System.out.println("All Jobs are done");
}
But note that if you don’t need the ExecutorService for subsequent tasks, it’s much easier to wait for all jobs to complete:
cachedThreadPool.shutdown();
cachedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
System.out.println("All Jobs are done");
But regardless of how unnecessary the manual tracking of the job status is, let’s delve into your attempt, so you may avoid the mistakes in the future:
if (countThreadMap.get(batchIterationTime) == null) {
The ConcurrentMap is thread safe, but this does not turn your concurrent code into sequential one (that would render multi-threading useless). The above line might be processed by up to all 32 threads at the same time, all finding that the key does not exist yet so possibly more than one thread will then be going to put the initial value into the map.
AtomicLong[] atomicThreadCountArr = new AtomicLong[2];
atomicThreadCountArr[0] = new AtomicLong(1);
atomicThreadCountArr[1] = new AtomicLong(System.currentTimeMillis());
countThreadMap.put(batchIterationTime, atomicThreadCountArr);
That’s why this is called the “check-then-act” anti-pattern. If more than one thread is going to process that code, they all will put their new value, being confident that this was the right thing as they have checked the initial condition before acting but for all but one thread the condition has changed when acting and they are overwriting the value of a previous put operation.
} else {
AtomicLong[] atomicThreadCountArr = countThreadMap.get(batchIterationTime);
atomicThreadCountArr[0].getAndAdd(1);
countThreadMap.put(batchIterationTime, atomicThreadCountArr);
Since you are modifying the AtomicInteger which is already stored into the map, the put operation is useless, it will put the very array that it retrieved before. If there wasn’t the mistake that there can be multiple initial values as described above, the put operation had no effect.
}
if (countThreadMap.get(batchIterationTime)[0].get() == 32) {
Again, the use of a ConcurrentMap doesn’t turn the multi-threaded code into sequential code. While it is clear that the only last thread will update the atomic integer to 32 (when the initial race condition doesn’t materialize), it is not guaranteed that all other threads have already passed this if statement. Therefore more than one, up to all threads can still be at this point of execution and see the value of 32. Or…
System.out.println("done");
countThreadMap.remove(batchIterationTime);
One of the threads which have seen the 32 value might execute this remove operation. At this point, there might be still threads not having executed the above if statement, now not seeing the value 32 but producing a NullPointerException as the array supposed to contain the AtomicInteger is not in the map anymore. This is what happens, occasionally…
After creating your 10 jobs, your main thread is still running - it doesn't wait for your jobs to complete before it calls report on the test. You try to overcome this with the while loop, but tPoolExecutor.getActiveCount() is potentially coming out as 0 before the workerThread is executed, and then the countThreadMap.size() is happening after the threads were added to your HashMap.
There are a number of ways to fix this - but I will let another answer-er do that because I have to leave at the moment.
Let us suppose that I have a thread that consumes items produced by another thread. Its run method is as follows, with inQueue being a BlockingQueue
boolean shutdown = false;
while (!shutdown) {
try {
WorkItem w = inQueue.take();
w.consume();
} catch (InterruptedException e) {
shutdown = true;
}
}
Furthermore, a different thread will signal that there are no more work items by interrupting this running thread. Will take() throw an interrupted exception if it does not need to block to retrieve the next work item. i.e. if the producer signals that it is done filling the work queue, is it possible to accidentally leave some items in inQueue or miss the interrupt?
A good way to signal termination of a blocking queue is to submit a 'poison' value into the queue that indicates a shutdown has occurred. This ensures that the expected behavior of the queue is honored. Calling Thread.interupt() is probably not a good idea if you care about clearing the queue.
To provide some code:
boolean shutdown = false;
while (!shutdown) {
try {
WorkItem w = inQueue.take();
if (w == QUEUE_IS_DEAD)
shutdown = true;
else
w.consume();
} catch (InterruptedException e) {
// possibly submit QUEUE_IS_DEAD to the queue
}
}
I wondered about the same thing and reading the javadoc for take() I believed that it would throw an interrupted exception only after having taken all the items in the queue, since if the queue had items, it would not have to "wait".
But I made a small test:
package se.fkykko.slask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
public class BlockingQueueTakeTest {
public static void main(String[] args) throws Exception {
Runner t = new Runner();
Thread t1 = new Thread(t);
for (int i = 0; i < 50; i++) {
t.queue.add(i);
}
System.out.println(("Number of items in queue: " + t.queue.size()));
t1.start();
Thread.sleep(1000);
t1.interrupt();
t1.join();
System.out.println(("Number of items in queue: " + t.queue.size()));
System.out.println(("Joined t1. Finished"));
}
private static final class Runner implements Runnable {
BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(100);
AtomicLong m_count = new AtomicLong(0);
#Override
public void run() {
try {
while (true) {
queue.take();
System.out.println("Took item " + m_count.incrementAndGet());
final long start = System.currentTimeMillis();
while ((System.currentTimeMillis() - start) < 100) {
Thread.yield(); //Spin wait
}
}
}
catch (InterruptedException ex) {
System.out.println("Interrupted. Count: " + m_count.get());
}
}
}
}
The runner will take 10-11 items and then finish i.e. take() will throw InterruptedException even if there still is items in the queue.
Summary: Use the Poison pill approach instead, then you have full control over how much is left in the queue.
According to javadoc, the take() method will throw InterruptedException if interrupted while waiting.
You can't in general interrupt the threads of an ExecutorService from external code if you used ExecutorService::execute(Runnable) to start the threads, because external code does not have a reference to the Thread objects of each of the running threads (see the end of this answer for a solution though, if you need ExecutorService::execute). However, if you instead use ExecutorService::submit(Callable<T>) to submit the jobs, you get back a Future<T>, which internally keeps a reference to the running thread once Callable::call() begins execution. This thread can be interrupted by calling Future::cancel(true). Any code within (or called by) the Callable that checks the current thread's interrupt status can therefore be interrupted via the Future reference. This includes BlockingQueue::take(), which, even when blocked, will respond to thread interruption. (JRE blocking methods will typically wake up if interrupted while blocked, realize they have been interrupted, and throw an InterruptedException.)
To summarize: Future::cancel() and Future::cancel(true) both cancel future work, while Future::cancel(true) also interrupts ongoing work (as long as the ongoing work responds to thread interrupt). Neither of the two cancel invocations affects work that has already successfully completed.
Note that once a thread is interrupted by cancellation, an InterruptException will be thrown within the thread (e.g. by BlockingQueue::take() in this case). However, you a CancellationException will be thrown back in the main thread the next time you call Future::get() on a successfully cancelled Future (i.e. a Future that was cancelled before it completed). This is different from what you would normally expect: if a non-cancelled Callable throws InterruptedException, the next call to Future::get() will throw InterruptedException, but if a cancelled Callable throws InterruptedException, the next call to Future::get() will through CancellationException.
Here's an example that illustrates this:
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
public class Test {
public static void main(String[] args) throws Exception {
// Start Executor with 4 threads
int numThreads = 4;
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numThreads);
try {
// Set up BlockingQueue for inputs, and List<Future> for outputs
BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
List<Future<String>> futures = new ArrayList<>(numThreads);
for (int i = 0; i < numThreads; i++) {
int threadIdx = i;
futures.add(executor.submit(new Callable<String>() {
#Override
public String call() throws Exception {
try {
// Get an input from the queue (blocking)
int val = queue.take();
return "Thread " + threadIdx + " got value " + val;
} catch (InterruptedException e) {
// Thrown once Future::cancel(true) is called
System.out.println("Thread " + threadIdx + " got interrupted");
// This value is returned to the Future, but can never
// be read, since the caller will get a CancellationException
return "Thread " + threadIdx + " got no value";
}
}
}));
}
// Enqueue (numThreads - 1) values into the queue, so that one thread blocks
for (int i = 0; i < numThreads - 1; i++) {
queue.add(100 + i);
}
// Cancel all futures
for (int i = 0; i < futures.size(); i++) {
Future<String> future = futures.get(i);
// Cancel the Future -- this doesn't throw an exception until
// the get() method is called
future.cancel(/* mayInterruptIfRunning = */ true);
try {
System.out.println(future.get());
} catch (CancellationException e) {
System.out.println("Future " + i + " was cancelled");
}
}
} finally {
// Terminate main after all threads have shut down (this call does not block,
// so main will exit before the threads stop running)
executor.shutdown();
}
}
}
Each time you run this, the output will be different, but here's one run:
Future 1 was cancelled
Future 0 was cancelled
Thread 2 got value 100
Thread 3 got value 101
Thread 1 got interrupted
This shows that Thread 2 and Thread 3 completed before Future::cancel() was called. Thread 1 was cancelled, so internally InterruptedException was thrown, and externally CancellationException was thrown. Thread 0 was cancelled before it started running. (Note that the thread indices won't in general correlate with the Future indices, so Future 0 was cancelled could correspond to either thread 0 or thread 1 being cancelled, and the same for Future 1 was cancelled.)
Advanced: one way to achieve the same effect with Executor::execute (which does not return a Future reference) rather than Executor::submit would be to create a ThreadPoolExecutor with a custom ThreadFactory, and have your ThreadFactory record a reference in a concurrent collection (e.g. a concurrent queue) for every thread created. Then to cancel all threads, you can simply call Thread::interrupt() on all previously-created threads. However, you will need to deal with the race condition that new threads may be created while you are interrupting existing threads. To handle this, set an AtomicBoolean flag, visible to the ThreadFactory, that tells it not to create any more threads, then once that is set, cancel the existing threads.
The java.concurrency.utils package was designed and implemented by some of the finest minds in concurrent programming. Also, interrupting threads as a means to terminate them is explicitly endorsed by their book "Java Concurrency in Practice". Therefore, I would be extremely surprised if any items were left in the queue due to an interrupt.