Some Concurrency Tips

Here is a review of some concurrency tips from Joshua Bloch, Brian Goetz and others.

Prefer immutable objects/data

Immutable objects do not change after construction. Immutable objects are simpler, safer, require no locks, and are thread safe. To make an object immutable don't provide setters/mutator methods, make fields private final, and prevent subclassing. If immutability is not an option, limit mutable state, less mutable state means less coordination.  Declare fields final wherever practical, final fields are simpler than mutable fields.

When threads share mutable data, each thread that reads or writes must coordinate access to the data. Failing to synchronize shared mutable data can lead to atomicity failures, race conditions, inconsistent state, and other forms of non-determinism. These erratic problems are among the most difficult to debug.

Limit concurrent interactions to well defined points, limit shared data, consider copying instead of sharing.

Threading risks for Web applications

A Servlet get, post, service method can be called for multiple clients at the same time. Multi-threaded Servlet Instance and Static variables are shared and therefore if mutable, access must be coordinated. Servlets are typically long-lived objects with a high thread load, if you over-synchronize performance suffers, try to either share immutable (final) data, or don’t share at all, request arguments and local variables are safer.

Hold Locks for as short a time as possible


Do as little work as possible inside synchronized regions.  Move code that doesn't require the lock out of synchronized block, especially if  time-consuming(!).


The Lock interface provides more extensive locking operations than using a synchronized block, one advantage is the ability to not block if a lock is not available.  You should obtain the lock, read or write shared data only as necessary, and unlock within a finally clause to ensure that the lock is released. Below is an example using a ReentrantReadWriteLock:



A way to reduce the time that a lock is held is lock splitting or lock striping, which uses different locks for state variables instead of a single lock. This reduces the lock granularity, allowing greater scalability but you must take locks in a disciplined order or risk deadlock.

Prefer executors and tasks to threads


Instead of working directly with threads, use the Java Concurrency Utilities Executor Framework. The Executor service decouples task submission from execution policy. Think in terms of runnable tasks and let an executor service execute them for you.



Executors can be created either directly or by using the factory methods in the Executors class:





Here is an example that uses the Executor, Executors and ExecutorService classes:

The example is a web service class that handles multiple incoming connections simultaneously with a fixed pool of threads. A fixed thread pool is initialized with the newFixedThreadPool method of the Executors class which returns an ExecutorService object. Incoming connections are handled by calling execute on the ExecutorService pool object, passing it a Runnable object. The Runnable object's run method processes the connection. When the run method completes the thread will automatically be returned to the thread pool. If a connection comes in and all threads are in use, then the main loop will block until a thread is freed.

Prefer Concurrency utilities to wait and notify


Whenever you are about to use wait and notify check and see if there is a class in java.util.concurrent that does what you need.  The concurrent collections provide high-performance concurrent implementations of standard collection interfaces such as List, Queue, and Map.



BlockingQueues are concurrent queues extended with blocking methods, which wait (or block) until an element becomes available for retrieving or space becomes available for storing.


Producer Consumer Pattern


Blocking queues are useful for the Producer Consumer Pattern where producer threads enqueue work items and consumer threads dequeue and process work items. Below is an example of a Consumer Pattern for a logger used by multiple threads. The Logger constructor takes a BlockingQueue as an input argument. In the run method messages are retrieved from the queue and logged. When the queue is empty the logging thread will block until an message becomes available for retrieving.


Below is an example of a Producer that uses the logger. A new ArrayBlockingQueue is instantiated for passing to the logger constructor. In the run method messages are put into the queue for logging. If the queue is full, the put will block until the logger has removed messages.


Synchronizers


Synchronizers are objects that help with coordinating access between threads. The most used synchronizers are CountDownLatch and Semaphore.  The use of synchronizers can eliminate most uses of wait or notify.


Below is an example of the use of a semaphore to control access to pool of resources. Multiple threads can request the use of a resource and return it when they have finished with it.

In the creator we create a new semaphore with the same size as the pool of resources we're creating.

In the getResource() method the semaphore aquire method is called to try to aquire a permit to use a resource. If there are resources available this will return and a resource will be returned from the pool. If all the resources are in use the call to aquire will block until another thread calls release on the semaphore.  When a thread finishes with a resource the resource is returned to the pool and the release method is called. Both aquire and release can be considered atomic operations.



Multithreaded Lazy Initialization is tricky

When threads share a lazily initialized field, access to the field must be synchronized, or non-determinism type bugs can result.



Prefer Normal initialization

Don't use lazy initialization unless an object or field is costly to initialize and not used often. Normally normal initialization is best ;) Below is a thread safe example of eager initialization for a singleton, the private final instance field and the private constructor make it immutable.




If you need to use lazy initialization for performance on a static field, use the initialize-on-demand holder pattern. This pattern takes advantage of the guarantee that a class will not be initialized until it is used :

References and More Information:

Effective Java, Second Edition by Joshua Bloch
Java Concurrency in Practice by Brian Goetz
Robust and Scalable Concurrent Programming: Lessons from the Trenches
Concurrency: Past and Present






Comments:

Fantastic article. Thanks for putting this up!

Posted by James on September 17, 2009 at 07:50 PM EDT #

Like it. Thanks for the pointers. Bookmarked.

Posted by Trevor Hinson on September 24, 2009 at 04:30 AM EDT #

Java concurrency in Practice book is really good.

Posted by Bhupender giri on October 24, 2009 at 04:04 PM EDT #

Post a Comment:
Comments are closed for this entry.
About

caroljmcdonald

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today