Wednesday, September 19, 2012

Java Concurrency in Practice - Summary - Part 4

NOTE: These summaries are NOT meant to replace the book.  I highly recommend buying your own copy of the book if you haven't already read it. 

Chapter 6 - Task Execution

  1. Individual client requests are a natural task boundary choice for server applications.
  2. Creating a new thread per task is usually NOT a good idea.
    1. Overheads of thread creation and teardown can add up to be quite significant.
    2. Active threads consume system resources like memory even when they are idle, and also increase CPU contention.
    3. Creating too many threads can result in an OutOfMemoryError.
  3. Primary abstraction for task execution in Java is the Executor framework, not Threads.
    1. Executor interface has a void execute(Runnable) methods.
  4. Executors are the easiest way to implement a producer-consumer design.
  5. Decouples task submission from execution.  Execution policy is separated from task submission.
  6. Always use Executor instead of new Thread(runnable).start()
  7. Different types of Thread Pools can be created using static factory methods of the Executors class:
    1. newFixedThreadPool - creates new threads upto a maximum specified size.  Tries to keep pool size constant by adding new threads if some die due to  exceptions.
    2. newCachedThreadPool - No bounds on number of threads.  Number of threads increase/decrease based on load.
    3. newSingleThreadExecutor - Used to process tasks sequentially in order imposed by task queue (FIFO, LIFO, priority order).  Replaces thread if it dies unexpectedly.
    4. newScheduledThreadPool - Fixed size thread pool that supports delayed and periodic task execution.
      1. Use instead of Timer.
        1. Timer creates only a single thread.  If one task takes too long, it affects the timing accuracy of subsequent TimerTasks.
        2. An unchecked exception thrown by a TimerTask terminates the Timer thread.  The Timer thread is not resurrected, and the Timer is simply cancelled.
        3.  Scheduled thread pools do not have the above two limitations  However,  Timers can schedule based on absolute time, while scheduled thread pools only support relative time.
  8. Executor lifecycle has 3 states - running, shutting down, terminated.
  9. ExecutorService interface (that extends Executor) offers methods like shutdown(), shutdownNow(), isShutdown(), isTerminated(), awaitTermination() to control Executor life cycle.
    1. shutdown() - graceful shutdown.  Allow all running and previously submitted tasks to complete.  No new tasks are accepted.
    2. shutdownNow() - cancel running tasks, and ignores any queued tasks.
    3. Tasks submitted to executor after shutdown are passed to a rejection handler, which may silently drop the task or throw a RejectedExecutionException.
    4. awaitTermination() is usually called immediately after calling shutdown().
  10. ExecutorService.submit(Callable) returns a Future. A  Future represents the lifecycle of a task, and provides methods to monitor/control it.
  11. CompletionService combines the functionality of an Executor and a BlockingQueue.  Submit a bunch of Callables to the Executor, and then wait for the results to be available using take() and poll().
    1. An ExecutorCompletionService is a wrapper around an Executor - new ExecutorCompletionService(executor).
    2. Tasks are submitted to the completion service, and not directly to the Executor.
    3. Multiple completion services can share an Executor.
    4. Keep track of the number of tasks submitted in order to determine the number of times to call take().
  12. Future.get() supports a version that throws a TimeoutException if the result is not available within the specified time delay.
    1. If a TimeoutException happens, then call cancel on the Future to cancel the task.  If the task is written to be cancelable, then it can be terminated to avoid consuming unnecessary resources.
  13. ExecutorService.invokeAll - takes a collection of tasks and returns a collection of Futures.  Timed version of invokeAll returns when all tasks have completed, the calling thread is interrupted or if the timeout expires.  Use Future.isCancelled() to determine if a particular task completed or was interrupted/cancelled.

No comments: