A broken CompletableFuture invocation with an unexpected fix

During a recent training, I was demonstrating the class CompletableFuture and how it could be used to create non-blocking methods like this:


    public static void <T> persist(T entity, Consumer<T> andThen) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("Persisting entity...");
            return entity;
        }).thenAccept(andThen);
    }

    public static void main(String[] args) {
        persist("toPersist", System.out::println);
        System.out.println("Done!");
    }

I wrote a method like the one above and ran the application. The output, unfortunately, was only:


    Done!

Where did the "Persisting entity..." output go?

(P.S.: The absolute worst feeling ever is when you create an example during a training, and it doesn't work! I definitely should have had a precreated example, but that is beside the point. =])

Since it can be hard to think when there are 30 people watching you, I did the first thing that came to mind. I created an ExecutorService and handed that reference into the thread pool. Now the code says:


    private static final ExecutorService pool = Executors.newCachedThreadPool();

    public void <T> persist(T entity, Consumer<T> andThen) {
        return CompletableFuture.supplyAsync(() -> {
            System.out.println("Persisting entity...");
            return entity;
        }, pool).thenAccept(andThen);
    }

And it worked! It was a bit of a shock, but I was pleased that I could fix it and move on to other topics. :)

After class, though, it gnawed at me, and I was excited when I finally got a minute to sit down and poke at it.

After peeling away a couple of layers, I found that I could reproduce the problem in the following way:


public static void main(String[] args) {
    ExecutorService pool = new ForkJoinPool(1); //Executors.newCachedThreadPool();

    pool.execute(() -> System.out.println("Done"));

    pool.shutdown();
}

This example will only print out "Done" occasionally. However, if I change it to:


public static void main(String[] args) {
    ExecutorService pool = Executors.newCachedThreadPool();

    pool.execute(() -> System.out.println("Done"));

    pool.shutdown();
}

It will work every time.

What's going on? Long story short, I posted my Fork/Join vs ThreadPoolExecutor question to Stack Overflow and John Vint gave the simple answer that all threads in the Fork Join pool are Daemon threads, which means that the VM will stop running without waiting for them to complete. The ThreadPoolExecutor creates non-deamon threads which causes the runtime to wait until they are done.

What does this mean for the in-class example I gave? If you need the runtime to hang on while your CompletableFuture is finishing, pass an Executors-obtained thread pool as a second parameter since by default it uses the Fork/Join common pool.

0 comments:

An Advanced Java Readlng List

10:55 PM , 0 Comments

Recently, I finished doing a 32-hour training over 8 days for a group for 30 Java professionals. We covered several topics, and I published to them a list of further reading that has influenced me as a developer over the years. Perhaps you will find value in the same list I shared with them:


  1. Head First Design Patterns - My co-workers and I studied this book a chapter a week together over lunch back in 2007. Decorators, Observers, and Strategies completely changed my perspective on how to develop code prepared for change.

  2. Effective Java (2nd Edition) - After having developed in Java for 14 years, I finally picked up Josh Bloch's book and after the first chapter I was sorry I had waited so long. It explained and validated many of my long held practices as well as introduced me to additional good ones, like the self-documenting power of public static final methods as named constructors.

  3. Java SE8 for Programmers (3rd Edition) (Deitel Developer Series) - Java 8 is the coolest Java release since Java 5. Method references and lambdas immediately changed the way I code for the better. In addition, java.time is the Date/Time API we've always wanted. Deitel's book is great at exposing all the great new features that will change the way you code in Java.

  4. Starting Out with Java: Early Objects (4th Edition) (Gaddis Series) - Alternatively, there were some attendees who wanted a good foundational Java book. I've used this book when teaching intro Java classes, and I like it! Especially, I like the early introduction to object-oriented programming.

  5. Java Concurrency in Practice - This is another eye-opening book that I waited far too long to read. Atomicity I understood, but I had never considered the ideas of visibility or re-ordering. And I was completely helped out by his explanation for what to do with InterruptedException.

  6. Java Performance: The Definitive Guide

  7. Iron-Clad Java: Building Secure Web Applications - I included this one because security is often tacked on after running some vulnerability assessment. While I might agree that one should "optimize after", security should be built in from the start.

  8. Mastering JavaServer Faces 2.2 - To be honest, I prefer an action-based framework like Spring MVC, but JSF 2.2 caught my eye, especially with the new HTML5-friendly JSF attribute syntax. More than that, this group of engineers asked for training on it, and this is a great book to get a more comprehensive view. :)

  9. REST in Practice: Hypermedia and Systems Architecture - This book is not quite as practical as the rest of them, but I really liked the theory outlined here, mixed with code examples. Through this book, I better understood the gaps that exist between existing libraries and what ReST specifies.

  10. Pro JPA 2: Mastering the Java(TM) Persistence API (Expert's Voice in Java Technology)

  11. Java Message Service - This book helped me understand an API that had to that point had seemed so inaccessible to me. That and Spring Boot made it super-easy! :)

Enjoy!

0 comments: