tl;dr
Using modern Java, define your task as a Callable
that returns a result. Submit your task objects to an executor service. That service returns a Future
object to track each task’s completion status and result. Collect these Future
objects.
Wait for your executor service to shut down. When done, loop your collected Future
objects to retrieve the results of each task.
Executor service
In modern Java, we rarely need to address the Thread
class directly. The executor service framework was created to handle the chore of juggling threads. See tutorial by Oracle.
Define your task as Runnable
, or as a Callable
when you expect a result to be returned.
When you submit a Callable
to an executor service, you get back a Future
object. Collect those Future
objects, as they will contain the result of your Callable
task object’s work.
To wait for all the tasks to be done, or canceled, or failed, call ExecutorSerivce#shutdown
and then ExecutorService#awaitTermination
. That first one tells the service to shut down after all submitted tasks are finished. The second blocks the flow-of-control to wait until all the tasks are finished and the service has been shut down. The second one takes time-out parameters, to force a shutdown if need be.
When past those calls, loop your collection of Future
objects. Ask each for its contents, the results of your tasks’ work.
In the following example, we have a simple Callable
that returns an Integer
object with some number we imagine to be calculated but for the sake of this demo is simply generated randomly. We sleep for a random few seconds, to simulate lots of real work that takes a while.
class IntegerProvider implements Callable < Integer >
{
@Override
public Integer call ( ) throws Exception
{
System.out.println( "INFO - Starting `call` method. " + Instant.now() );
// Pretend we have lots of work to do by putting this thread to sleep a few seconds.
Duration duration = Duration.ofSeconds( ThreadLocalRandom.current().nextInt( 1 , 7 ) );
Thread.sleep( duration.toMillis() );
// Now pretend we did some work to calculate a number as a result.
int i = ThreadLocalRandom.current().nextInt( 7 , 42 );
Integer result = java.lang.Integer.valueOf( i );
return result;
}
}
Then we have some code to start an executor service, and submit several of these IntegerProvider
task objects. When done, we sum total of their results.
ExecutorService executorService = Executors.newFixedThreadPool( 3 );
int limit = 20;
List < Future < Integer > > futures = new ArrayList <>( limit );
for ( int i = 0 ; i < limit ; i++ )
{
IntegerProvider callableTask = new IntegerProvider();
Future < Integer > future = executorService.submit( callableTask );
futures.add( future );
}
// Wait for submitted tasks to be done/canceled/failed.
executorService.shutdown();
try { executorService.awaitTermination( 1 , TimeUnit.HOURS ); } catch ( InterruptedException e ) { e.printStackTrace(); }
// At this point, all the submitted tasks are done. Process results.
int total = 0;
for ( Future < Integer > future : futures )
{
// Process each `future` object. Get the result of each task's calculation. Sum total.
if ( future.isCancelled() )
{
System.out.println( "Oops, this future is canceled." );
} else if ( future.isDone() )
{
try { total = ( total + future.get() ); }
catch ( InterruptedException e ) { e.printStackTrace(); }
catch ( ExecutionException e ) { e.printStackTrace(); }
} else
{
System.out.println( "ERROR - Should never reach this point." );
}
}
System.out.println( "total = " + total );
Here is a complete single class to demonstrate.
package work.basil.numbers;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class App
{
public static void main ( String[] args )
{
App app = new App();
app.demo();
}
private void demo ( )
{
System.out.println( "INFO - Starting `demo` method. " + Instant.now() );
ExecutorService executorService = Executors.newFixedThreadPool( 3 );
int limit = 20;
List < Future < Integer > > futures = new ArrayList <>( limit );
for ( int i = 0 ; i < limit ; i++ )
{
IntegerProvider callableTask = new IntegerProvider();
Future < Integer > future = executorService.submit( callableTask );
futures.add( future );
}
// Wait for submitted tasks to be done/canceled/failed.
executorService.shutdown();
try { executorService.awaitTermination( 1 , TimeUnit.HOURS ); } catch ( InterruptedException e ) { e.printStackTrace(); }
// At this point, all the submitted tasks are done. Process results.
int total = 0;
for ( Future < Integer > future : futures )
{
// Process each `future` object. Get the result of each task's calculation. Sum total.
if ( future.isCancelled() )
{
System.out.println( "Oops, this future is canceled." );
} else if ( future.isDone() )
{
try { total = ( total + future.get() ); }
catch ( InterruptedException e ) { e.printStackTrace(); }
catch ( ExecutionException e ) { e.printStackTrace(); }
} else
{
System.out.println( "ERROR - Should never reach this point." );
}
}
System.out.println( "total = " + total );
System.out.println( "INFO - Ending `demo` method. " + Instant.now() );
}
class IntegerProvider implements Callable < Integer >
{
@Override
public Integer call ( ) throws Exception
{
System.out.println( "INFO - Starting `call` method. " + Instant.now() );
// Pretend we have lots of work to do by putting this thread to sleep a few seconds.
Duration duration = Duration.ofSeconds( ThreadLocalRandom.current().nextInt( 1 , 7 ) );
Thread.sleep( duration.toMillis() );
// Now pretend we did some work to calculate a number as a result.
int i = ThreadLocalRandom.current().nextInt( 7 , 42 );
Integer result = java.lang.Integer.valueOf( i );
return result;
}
}
}
When run.
INFO - Starting `demo` method. 2021-03-28T22:47:21.717806Z
INFO - Starting `call` method. 2021-03-28T22:47:21.743966Z
INFO - Starting `call` method. 2021-03-28T22:47:21.744736Z
INFO - Starting `call` method. 2021-03-28T22:47:21.744890Z
INFO - Starting `call` method. 2021-03-28T22:47:22.745855Z
INFO - Starting `call` method. 2021-03-28T22:47:24.749539Z
INFO - Starting `call` method. 2021-03-28T22:47:24.749522Z
INFO - Starting `call` method. 2021-03-28T22:47:25.749902Z
INFO - Starting `call` method. 2021-03-28T22:47:26.749692Z
INFO - Starting `call` method. 2021-03-28T22:47:26.750092Z
INFO - Starting `call` method. 2021-03-28T22:47:27.755313Z
INFO - Starting `call` method. 2021-03-28T22:47:28.752061Z
INFO - Starting `call` method. 2021-03-28T22:47:28.752030Z
INFO - Starting `call` method. 2021-03-28T22:47:28.757104Z
INFO - Starting `call` method. 2021-03-28T22:47:29.761592Z
INFO - Starting `call` method. 2021-03-28T22:47:30.755099Z
INFO - Starting `call` method. 2021-03-28T22:47:30.755230Z
INFO - Starting `call` method. 2021-03-28T22:47:31.756692Z
INFO - Starting `call` method. 2021-03-28T22:47:32.758877Z
INFO - Starting `call` method. 2021-03-28T22:47:33.760871Z
INFO - Starting `call` method. 2021-03-28T22:47:35.764750Z
total = 519
INFO - Ending `demo` method. 2021-03-28T22:47:38.770971Z