4

I have to run x asynchronous threads , which can last several minutes each.
They ALL have to terminate properly without exceptions.

If one fails I have to stop them all immediately and not wait minutes for them to finish a job that will be useless. How can I do it? This is a piece of my current code:

ExecutorService executorService = Executors.newFixedThreadPool(iNumThread);
List<Callable<String>> callableTasks = new ArrayList<>();
for (int i = 0; i < iNumThread; i++) {
    callableTasks.add(new ExecuteJob(...));  
}

List<Future<String>> resultList = null;
try {
    resultList = executorService.invokeAll(callableTasks);
} catch (InterruptedException ex) {
    ex.printStackTrace();
}
executorService.shutdown();

// This cycle doesn't work for me because it waits for all threads to finish even if one or more have given an exception. 
while (!executorService.isTerminated()) {
    //DO SOMETHING LIKE UPDATE UI, ETC
}
//check result
for (int i = 0; i < resultList.size(); i++) {
    Future<String> future = resultList.get(i);
    try {
        String result = future.get();
        if (!result.equals("OK")){
            //ohi ohi
        }        
    } catch (InterruptedException | ExecutionException e) {
        //ohi ohi
        e.printStackTrace();
    }
}

What I had thought was to put the for loop inside the while loop but it seems to me that the future.get() method blocks the execution of the thread and I want to keep it faster as possible.

I hope I've made myself clear. Thank you very much.

Max

5
  • Future.get() - Waits if necessary for the computation to complete, and then retrieves its result. You probably want to combine it with Future.isDone() check. Commented Nov 13 at 12:29
  • Maybe using CompletableFuture. You can then use CompletableFuture.anyOf to wait. see Commented Nov 13 at 12:29
  • 1
    It's a shame it's still a preview feature, because this sounds like a job for Structured Concurrency: openjdk.org/jeps/525 Commented Nov 13 at 18:55
  • 1
    @RobSpoor A Preview feature in Java is fully implemented and fully tested. So you can choose to use it in production. Just be prepared to make updates if future versions of Java change (or remove) the feature. This Question is indeed solved by Structured Concurrency. Commented Nov 13 at 22:59
  • @BasilBourque indeed. There is a slight chance a feature gets scrapped (string templates anyone?), but the odds of that are not high, especially if there have been 5+ preview versions like this one. Commented Nov 14 at 11:05

1 Answer 1

4

java.util.concurrent.ExecutorCompletionService

You can use ExecutorCompletionService for that. Here is a little example. Let's first create a simple ExecuteJob class for demonstration.

ExecuteJob.java

public class ExecuteJob implements Callable<String> {

    private final int ID;

    public ExecuteJob(int ID) {
        this.ID = ID;
    }

    @Override
    public String call() {
        try {
            Thread.sleep(1000L * this.ID);

            // Let's fail the third thread for demonstration
//            if (this.ID == 2) {
//                throw new RuntimeException();
//            }

            return "OK\t" + (System.currentTimeMillis() / 1000);
        } catch (Exception e) {
            return "NOT OK\t" + (System.currentTimeMillis() / 1000);
        }
    }
}

This class when executed, sleeps for ID seconds to mimic a running job. I will explain the commented out part later.

App.java

private final static int iNumThread = 6;

public static void main(String[] args) throws InterruptedException, ExecutionException {
    ExecutorService es = Executors.newFixedThreadPool(iNumThread);
    ExecutorCompletionService<String> ecs = new ExecutorCompletionService<>(es);

    for (int i = 0; i < iNumThread; i++) ecs.submit(new ExecuteJob(i));

    for (int i = 0; i < iNumThread; i++) {
        Future<String> f = ecs.take(); // returns as soon as any task finishes
        String result = f.get();
        System.out.println(result);

        if (!result.startsWith("OK")) {
            System.out.println("One thread failed, cancelling all threads...");
            es.shutdownNow();
            break;
        }
    }

    System.out.println("Gracefully shutting down threads...");
    es.shutdown();
}

Here you can see that we use ExecutorCompletionService for execution of our tasks. submit() method sends a task to run in the background and immediately returns a Future you can use to get its result or cancel it.

Now when we run this code as is (there is a commented out part on ExecuteJob.java) the output is like this:

OK  1763043309
OK  1763043310
OK  1763043311
OK  1763043312
OK  1763043313
OK  1763043314
Gracefully shutting down threads...

Process finished with exit code 0

There is one second delay between each thread (because of the Thread.sleep() on the ExecutorJob, these are still running asynchronous). Let's un-comment those lines at the ExecutorJob and see what happens.

OK  1763043471
OK  1763043472
NOT OK  1763043473
One thread failed, cancelling all threads...
Gracefully shutting down threads...

Process finished with exit code 0

Now when we purposely shut down a thread (the third one) the program doesn't wait for rest of them and shuts all of the threads. Note that ExecutorService.shutdown() method gracefully shuts down the threads. As it says in the docs:

Initiates an orderly shutdown in which previously submitted tasks are executed, but no new tasks will be accepted.

ExecutorService.shutdownNow() on the other hand executes all threads immediatelly.

Attempts to stop all actively executing tasks, halts the processing of waiting tasks, and returns a list of the tasks that were awaiting execution. This method does not wait for actively executing tasks to terminate.

I hope this answers your question. I may have skip some try/catch for the simplicity of the answer. You should use them in your code. Feel free to ask any question.

Sign up to request clarification or add additional context in comments.

2 Comments

I take advantage of your kindness. In my ExecuteJob (the Callable) I've a counter and i want to update a progress bar getting the value of the counter in each tasks every x sec. In my case, but in general, how can I access to a property of a task while the program execution is stopped to waiting in Future<String> f = ecs.take(); ??
If you're using Java Swing for your UI be aware that the Swing is single threaded and it runs on EDT (Event Dispatch Thread). Heavy tasks must not run on the EDT or the UI freezes. I recommend you use SwingWorker for your background tasks. It has doInBackground() method for your heavy tasks that should be done in the background. And publish() and process() methods for updating UI. I think you should get rid of ExecutorCompletionService and use SwingWorker.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.