I am using ExecutorService in my class to async a few Callable tasks and then once all the tasks are complete, complete the parent process. Something like this
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
class SampleService implements Callable<String>{
private String dayOfWeek;
public SampleService(String dayOfWeek) {
this.dayOfWeek = dayOfWeek;
}
@Override
public String call() throws Exception {
if("Holiday".equals(dayOfWeek)){
throw new RuntimeException("Holiday is not valid day of week");
} else{
Random random = new Random();
int sleepTime = random.nextInt(60000);
Thread.sleep(sleepTime);
System.out.println("Thread "+dayOfWeek+" slept for "+sleepTime);
return dayOfWeek+" is complete";
}
}
}
class Scratch {
static ExecutorService executor = null;
public Scratch() {
executor = Executors.newFixedThreadPool(8);
}
public static void main(String[] args) {
List<String> days = Arrays.asList("Monday","Tuesday","Wednesday","Thursday","Friday","Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p,executor))
.collect(Collectors.toList());
long startTime = System.currentTimeMillis();
while(true || (System.currentTimeMillis()-startTime) < 60000){
boolean complete = true;
for(Future<String> future : completables){
complete = complete && future.isDone(); // check if future is done
}
if(complete){
System.out.println(" all tasks complete");
break;
}
}
long endTime = System.currentTimeMillis();
System.out.println("Time taken to get response from all threads "+ (endTime - startTime));
try{
for(Future<String> future : completables){
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static Future<String> createFuture(String p, ExecutorService executor) {
SampleService service = new SampleService(p);
return executor.submit(service);
}
}
It is working as expected.
The above example is just from a scratch file but i have something very similar. now i have kept the ThreadPoolExecutor as an instance object as it is being called multiple times and i do not want to create a new executor for each call. I would like to know are there any implications if i do not terminate or shutdown the executor when the main service class is terminated/ready for GC. I wanted to use the finalize method but it is deprecated now. So in this case what is the best approach to shutdown the executor when the enclosing class is GC without using finalize method?
ExecutorService
Yes, it is important to shutdown the executor when it is no longer needed. In this example (once the NullPointerException
is fixed), when the main
method exits, the process does not terminate because the thread pool has not been shut down. As the documentation for Executors.newFixedThreadPool
says:
The threads in the pool will exist until it is explicitly
shutdown
.
You are correct that the use of the finalize
method is deprecated and will be removed in a future version of Java. The best approach to shutting it down is to explicitly call shutdown
when it is no longer needed, in a finally
block to ensure it happens regardless of whether execution completes successfully or throws:
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(8);
try {
List<String> days = Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p, executor))
.collect(Collectors.toList());
// code omitted for brevity
try {
for (Future<String> future : completables) {
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
executor.shutdown();
}
}
In Java 19 or later, you can also use the try-with-resources construct:
public static void main(String[] args) {
try (ExecutorService executor = Executors.newFixedThreadPool(8)) {
List<String> days = Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p, executor))
.collect(Collectors.toList());
// code omitted for brevity
try {
for (Future<String> future : completables) {
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In a more complete application, the application component that creates the ExecutorService
may require its own shutdown method to trigger the shutdown of the ExecutorService
. It's common for this requirement to cascade through several layers of the application. Most application frameworks support some sort of component lifecycle management for this reason.
There are other issues with the code provided:
while(true || (System.currentTimeMillis()-startTime) < 60000)
is equivalent to while (true)
. The (System.currentTimeMillis()-startTime) < 60000
condition has no effect. Future
for completion in a loop is called "busy waiting" , and is usually discouraged because it consumes a high level of CPU. If you watch a CPU monitor while running this program, you will notice a spike in usage, despite the fact that the program doesn't do very much other than wait. It is generally preferable to use a method that blocks the calling thread until the condition is satisfied, when possible. In this case, Future.get
does exactly that.InterruptedException
is not recommended, as it defeats the purpose of interrupting a thread as a signal to terminate. In this case, I would allow it to propagate and terminate the program.Putting these together, here is the alternative implementation:
public static void main(String[] args) throws InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(8);
try {
List<String> days = Arrays.asList("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Holiday");
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p, executor))
.collect(Collectors.toList());
long startTime = System.currentTimeMillis();
for (Future<String> future : completables) {
try {
future.get();
} catch (ExecutionException e) {
// ignore it here, it will be printed below
}
}
System.out.println(" all tasks complete");
long endTime = System.currentTimeMillis();
System.out.println("Time taken to get response from all threads " + (endTime - startTime));
try {
for (Future<String> future : completables) {
String text = future.get();
System.out.println(text);
}
} catch (ExecutionException e) {
e.printStackTrace();
}
} finally {
executor.shutdown();
}
}
This preserves the behaviour of printing the results all at once at the end, rather than printing each result in order as it becomes available. If the latter behaviour is acceptable, then you can simplify this further into a single loop.
I would like to know are there any implications if i do not terminate or shutdown the executor when the main service class is terminated/ready for GC.
You certainly should be using a single ExecutorService
. That's the whole point of it. You should be submitting your jobs to the service until you are done and then you should immediately shutdown the service.
List<Future<String>> completables = days.stream()
.map(p -> createFuture(p,executor))
.collect(Collectors.toList());
// this will shutdown the executor while the submitted jobs run in the background
executor.shutdown();
Also you really done need the done checking look. Going through your futures and calling get()
will do that for you:
// you don't need this
for(Future<String> future : completables){
// also don't use this sort of logic. works fine. very hard to read.
complete = complete && future.isDone(); // check if future is done
}
Also, I don't think you should be using the term GC. You should talk about the main method exiting but we really rarely have to worry about GC issues unless your goal really is to reduce the object bandwidth of a high performance program or if you have a memory leak.
One additional comment. You should never initialize static fields in an instance constructor because if you call new Scratch()
twice, it will overwrite the static field.
class Scratch {
// should be initialized here or in static { } block
// always use final if you can
private final static ExecutorService executor = Executors.newFixedThreadPool(8);
public Scratch() {
// you should not initialize static fields in an instance constructor
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.