Showing posts with label multi-threading. Show all posts
Showing posts with label multi-threading. Show all posts

Wednesday, May 18, 2016

Java Multithreading in Practice: Part 3

Introduction - The Saga Continues

In Part 1 we discussed presenting a cleaner interface to Java 6's ExecutorService and ScheduledExecutorService.  In Part 2 we discussed ensuring that the system doesn't eat / ignore exceptions in threads.  In Part 3 we will go over reusing the thread pool... because that's the point of a thread pool.

Background

We don't read ALL of the documentation on a given class, we read parts and whatever the IDE gives
Thread Pool, not to be confused with Deadpool
us on a mouse-over.  This habit led me to reading the method-level documentation of ExecutorService but not the class-level documentation.  This is important later.

Hypothesis

The ExecutorService will have an equivalent of Thread.join(), and then be re-usable since that is the whole point of a thread pool.  A quick skim of shutdown() says to use awaitTermination().  Upon reading the full docs of awaitTermination() it appears that this is what I am looking for.

Results

Running threads after a while mysteriously fail with a ThreadPool size of 0.  I never set the size to 0, why would it do that?  After struggling with this and creating more unit tests to narrow the problem down for a full day (full day) I find that awaitTermination() terminates the WHOLE POOL instead of terminating the currently running threads.  What's the point of having a terminated pool?  Isn't that what close() is for?  Does this have a close()?  Why not?  Maybe because it is in the java.io package instead of java.concurrent?  Again, the entire point of having a thread pool is to reuse the threads!

Conclusion

In addition to needing to keep a List<Callable<Void>> I need to keep a List<Future<Void>> and loop through all of the Futures to cancel() each one.  WHAT?  Again, nothing in the documentation of awaitTermination() says that it terminates the thread pool itself.  That is buried in the class level documentation.  I end up adding a cancelThreads() method to our custom ThreadPool object leaving us with a final API like the following:
public class ThreadPool {

    ThreadPool(int size) {...}

    void add(Runnable) {...}

    void add(Callable<Void>){...}

    void clear(){...}

    boolean isEmpty(){...}

    void runSynchronously(){...}

    void runAsynchronously(){...}

    Callable&ltVoid> toSafeCallable(Callable<Void&gt){...}

    Runnable toSafeRunnable(Runnable){...}

    void cancelThreads(){...}

    void close(){...}

Wednesday, April 27, 2016

Java Multithreading in Practice: Part 2

In the previous Java Multithreading in Practice we discussed simplifying the base Java API for increased readability and reliability.  However, after further analysis catching exceptions was still not full-proof.  The end-user (of the API) would still need to remember to catch Exception in their Runnables and Callable<Void>s.  A working solution is to automatically do that internally in the custom ThreadPool class (not Java's ThreadPool class) with two protected methods, toSafeRunnable(Runnable) and toSafeCallable(Callable<Void>).  All these do is wrap the passed in argument in try catch block, catch Exception and log it as an error.  This lets the end user not have to worry about exceptions being lost in the system.

PS

In-line code is now monospaced and green to let it stand out. I hope that this helps people read the blog!

Wednesday, April 20, 2016

Java 8 Multi-Threading in Practice

Dealing with multithreading and race conditions is a famously hard and tricky subject for computer science.  On top of that, Java has some odd and unexpected implementation details in this area as well.

The first thing I noticed is that my engine was taking over 40 seconds to shut down after a Ctrl-C.  Where was the interruption going?  It turns out that the forEach lambda, apparently as part of it's functionalness, completely ignores interruptions until it is completely done with its tasks.  This means that the new stream APIs are not useful for long-running tasks (e.g. the kinds of tasks you want to run in parallel in the first place) without a work-around, such as checking manually if a thread is interrupted or a thread pool is shutdown (see below).

The second thing I noticed that was when a Callable was being executed by and the executor service was shut down, the Callable's Thread.currentInstance().isInterrupted() would return false, even if actively checked!  As a work-around I had to pass a reference to the executor service into the Callable and check ExecutorService.isShutdown() instead.  In my particular code-base this caused a circular reference but the modern garbage collector uses mark-and-sweep instead of reference counting so no memory leaks happen.
It could be worse [1].
ExecutorService.invokeAll()

Lastly the APIs can't decide if they want Runnables or Callables.  ExecutorService.invokeAll() only takes Callables and ScheduledExecutorService.scheduleAtFixedRate() only takes Runnables.  Of course I have the same logic that sometimes needs to be run all at once and sometimes needs to be run repeatedly so I need to convert between the two.  It looks like if an exception does bubble out of a Runnable.run() call, it is simply ignored.  This caused some strange system behavior until I made a habit of catching Exception and logging an error.  Of course, we hate to catch Exception.  For this reason I strongly prefer Callable, despite the debate on StackOverflow.

The solution was to create a ThreadPool class and ScheduledThreadPool sub class in my project's utils module to simplify all of this complexity and automatically convert between Runnables and Callables as needed.  After I made these classes and had the code base use them it was easier to think about how the system worked instead of being mired in the details of the Java API.

[1] Dragon Riders of Pern by FanDragonBall

Tuesday, March 8, 2016

Java Lambdas and Interrupted Exceptions

I couldn't figure out why my application was taking 40+ seconds to shut down.  It turned out that a long-running process was in a Lambda expression and was completely ignoring the owning thread being shut-down.  Changing the code to a foreach loop fixed the problem.

It looks like foreach loops are still useful in Java 8 after all.

Short post today - not feeling well.