1

This is my simple code:

public class Main4 {
    public static void main(String[] args) {
        System.out.println("Hello from thread: "+Thread.currentThread().getName());
        new Game().run();
        System.out.println("I am dying ... ");
    }

    static class Game {
        public void run() {
            value();
        }

        private int value() {
            int number = 0;
            CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> System.out.println("I am done, and my value is " + i));
            return number;
        }

        private CompletionStage<Integer> calculate() {
            CompletionStage<Integer> completionStage = new CompletableFuture<>();
            Executors.newCachedThreadPool().submit(() -> {
                System.out.println("I am in the thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(50000);
                    ((CompletableFuture<Integer>) completionStage).complete(3);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;

            });
            return completionStage;
        }
    }
}

The output of the execution is:

Hello from thread: main
I am in the thread: pool-1-thread-1
I am dying ... 

But the thing is: the main thread doesn't terminate immediately, it waits for 50000 milliseconds. that is my question. I understood that it should terminate because there is no more things to execute.

Initially I thought that the reason is that the "sleep" is being executed in the main thread, and that's why I printed the names of the threads, and they're two different threads.

Help is appreciated.

4

2 回答 2

6

I added time marks to the output of your program and also a shutdown hook so that JVM termination can be logged as well:

0s Hello from thread: main              # `main` method starts
0s I am in the thread: pool-1-thread-1  # `Runnable` submitted to the executor starts
0s I am dying ...                       # `main` method exits
50s I am done, and my value is 6        # `Runnable` submitted to the executor finishes
110s exiting                            # JVM process exits

Non-daemon threads

The reason why the process continues after main method exits is that JVM needs to wait for all non-daemon threads to terminate before it shuts down. Executors produced using Executors class create non-daemon threads by default (see Executors.defaultThreadFactory() method javadoc).

Customizing ThreadFactory

You can override how the threads are created by passing a custom ThreadFactory to the Executors.newCachedThreadPool() method:

ExecutorService executorService = Executors.newCachedThreadPool(runnable -> {
    Thread t = new Thread(runnable);
    t.setDaemon(true);
    return t;
});

executorService will only have daemon threads in its thread pool.

Thread caching

But notice that the JVM still doesn't exit for 60 seconds after your thenAccept block is executed:

50s I am done, and my value is 6        # `Runnable` submitted to the executor finishes
110s exiting                            # JVM process exits

Why is that? It's explained in Executors.newCachedThreadPool() documentation (emphasis added):

Creates a thread pool that creates new threads as needed, but will reuse previously constructed threads when they are available. These pools will typically improve the performance of programs that execute many short-lived asynchronous tasks. Calls to execute will reuse previously constructed threads if available. If no existing thread is available, a new thread will be created and added to the pool. Threads that have not been used for sixty seconds are terminated and removed from the cache. Thus, a pool that remains idle for long enough will not consume any resources. Note that pools with similar properties but different details (for example, timeout parameters) may be created using ThreadPoolExecutor constructors.

This means that this thread pool does not dispose of the threads immediately after they finish doing scheduled tasks. It will instead do it's best to reuse previously created threads for new task submissions. This is the reason for the delay: after your task finishes the thread remains in the thread pool for reusal and only after the next 60 seconds it is destroyed (you only submit one task in your program). Only then the JVM can exit (because the thread is not a daemon thread as pointed above).

Shutting down the ExecutorService

Normally when working with an ExecutorService you should shut it down explicitly before the process is terminated. For this purpose use ExecutorService.shutdown() or ExecutorService.shutdownNow() methods. Refer to the documentation for the difference between these two.

References

What is a daemon thread in Java?

Turning an ExecutorService to daemon in Java

Program does not terminate immediately when all ExecutorService tasks are done

Modified program with time marks and JVM termination log:

public class Main {
    private static final Instant start = Instant.now();

    private static void debug(String message) {
        System.out.println(Duration.between(start, Instant.now()).getSeconds() + "s " + message);
    }

    public static void main(String[] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(() -> debug("exiting")));
        debug("Hello from thread: "+Thread.currentThread().getName());
        new Game().run();
        debug("I am dying ... ");
    }

    static class Game {
        public void run() {
            value();
        }

        private int value() {
            int number = 0;
            CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> debug("I am done, and my value is " + i));
            return number;
        }

        private CompletionStage<Integer> calculate() {
            CompletionStage<Integer> completionStage = new CompletableFuture<>();
            Executors.newCachedThreadPool().submit(() -> {
                debug("I am in the thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(50000);
                    ((CompletableFuture<Integer>) completionStage).complete(3);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;

            });
            return completionStage;
        }
    }
}
于 2019-11-09T09:40:11.200 回答
1

the main thread doesn't terminate immediately

That's because you create an ExecutorService which you never shut down. Here is your code with the inclusion of shutting down the ExecutorService.

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main4 {
    public static void main(String[] args) {
        System.out.println("Hello from thread: "+Thread.currentThread().getName());
        new Game().run();
        System.out.println("I am dying ... ");
    }

    static class Game {
        public void run() {
            value();
        }

        private int value() {
            int number = 0;
            CompletionStage<Void> c = calculate().thenApply(i -> i + 3).thenAccept(i -> System.out.println("I am done, and my value is " + i));
            return number;
        }

        private CompletionStage<Integer> calculate() {
            CompletionStage<Integer> completionStage = new CompletableFuture<>();
            ExecutorService es = Executors.newCachedThreadPool();
            es.submit(() -> {
                System.out.println("I am in the thread: " + Thread.currentThread().getName());
                try {
                    Thread.sleep(50000);
                    ((CompletableFuture<Integer>) completionStage).complete(3);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                return null;

            });
            es.shutdown();
            return completionStage;
        }
    }
}

The output from the above code is...

Hello from thread: main
I am dying ... 
I am in the thread: pool-1-thread-1
I am done, and my value is 6
于 2019-11-09T09:52:47.100 回答