Search |
||
Some Java Concurrency TipsPosted by caroljmcdonald on September 17, 2009 at 1:55 PM PDT
Here is a review of some concurrency tips from Joshua Bloch, Brian
Goetz and others.
Prefer immutable objects/dataImmutable 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 applicationsA 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 possibleDo 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 threadsInstead 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 notifyWhenever 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 PatternBlocking 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. ![]() SynchronizersSynchronizers 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. ![]() |
||
|
|
Good