Skip to content

Instantly share code, notes, and snippets.

@sshark
Forked from danieldietrich/BigFTest.md
Created February 27, 2017 02:37
Show Gist options
  • Save sshark/77a60d506780dc0dd7c66858a0d7e726 to your computer and use it in GitHub Desktop.
Save sshark/77a60d506780dc0dd7c66858a0d7e726 to your computer and use it in GitHub Desktop.
Javaslang's Future: Thread creation and ExecutorService usage

Future Test

public class BigFTest {

    public static void main(String[] args) throws Throwable {
        final long sleepMillis = 1500;

        ExecutorService executorService = Executors.newFixedThreadPool(1);

        executorService.submit(() -> {  // bigF
            Try.run(() -> Thread.sleep(sleepMillis));
            Option.of(-10).toTry().flatMap(t -> Try.run(() -> foo(t))).get();
        });

        Future.run(() -> Thread.sleep(sleepMillis)).onComplete(ignored -> System.out.println("Slang future completed " + ignored));  // #1

        executorService.shutdown();
        System.out.println("Normal termination? " + executorService.awaitTermination(2, TimeUnit.SECONDS));

    }
    private static void foo(Integer t) {
    }
}

Execution 1: Unmodified example

Output:

Normal termination? true
Slang future completed Success(null)

Observation:

The application quits after 60 seconds. This is hard-baked behavior of Java's Executors.newCachedThreadPool(), which is Javaslang's default ExecutorService. In particular it is used in line #1 where we not pass a specific executor service: Future.run(() -> ...).

Execution 2: Comment out line #1

Output:

Normal termination? true

Observation:

As expected, the application quits immediately because we do not execute Future.run(() -> ...) anymore, which would use the default ExecutorService under the hood, that waits for 60 seconds.

However, it can be fixed by shutting down the Javaslang's default ExecutorService:

Future.DEFAULT_EXECUTOR_SERVICE.shutdown();
System.out.println("Normal termination? " + Future.DEFAULT_EXECUTOR_SERVICE.awaitTermination(2, TimeUnit.SECONDS));

Execution 3: Pass our specific ExecutorService to Future.run()

Future.run(executorService, () -> Thread.sleep(sleepMillis)).onComplete(ignored -> System.out.println("Slang future completed " + ignored));  // #1

Output:

Normal termination? true

Observation:

We are missing the output

Slang future completed Success(null)

This is because we have a fixed thread-pool having only one available thread. In our execution the first Thread

executorService.submit(() -> {  // bigF
  ...
});

still runs when we call executorService.shutdown(). Especially the Future.run(...) is rejected by the ExecutorService because there is no Thread available.

Question: Does it help to increase the fixedThreadPool size in order to run Future.run(...)?

Execution 4: We use of fixed thread pool of size 2 and re-run Execution 3

ExecutorService executorService = Executors.newFixedThreadPool(2);
...
Future.run(executorService, () -> Thread.sleep(sleepMillis)).onComplete(ignored -> System.out.println("Slang future completed " + ignored));  // #1

Output:

Normal termination? true

Observation:

We are still missing the output Slang future completed Success(null), what is going on here?

The answer is that Future.run() first runs Thread.sleep(), in the meanwhile the main thread calls executorService.shutdown().

When it comes to the onComplete() call that should outputs Slang future completed Success(null) the Future's current behavior is to start a new Thread for that output. But that is not possible, because the ExecutorService is already shutdown and rejects new Threads.

Scala's behavior is the same, but we want to change that in Javaslang for release 2.1.0. The onComplete() handler should be executed on the same Thread the Future has been run. More details here: see #1530

We want to go even one step further and let the user decide if new threads should be created for subsequent operations: see #1537

Note:

In order to verify our observation we could force the main thread to wait longer than the Future.run() needs to complete:

Try.run(() -> Thread.sleep(sleepMillis * 3));
executorService.shutdown();

Then the executorService is still able to run the onComplete() handler on a new thread. Output:

Slang future completed Success(null)
Normal termination? true

I hope this helps to understand the behavior of Future composition regarding new Thread creation and internal usage of an ExecutorService.

Cheers

- Daniel

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment