concurrency
DESCRIPTION
TRANSCRIPT
Concurrency[Effective Java by Joshua Bloch]
2
Synchronize access to shared mutable data
Item 1
3
Synchronization ensures:
1) Mutual exclusion i.e. to prevent an object from being observed in an
inconsistent state while it’s being modified by another thread.
2) Reliable communication between the threads i.e. each thread entering a
synchronized method or block sees the effects of all
previous modifications that were guarded by the same lock.
When the JVM executes a synchronized method, it acquires a lock on that
object. if one synchronized method owns a lock, no other synchronized method can
run until the lock is released only one lock on an object at a time lock is released when the method is finished
4
FEW MISCONCEPTIONS (SHOULD BE AVOIDED):
To improve performance, do not avoid synchronization when reading or writing
atomic data. Coz the language specification i.e. memory model guarantees that
a thread will not see an arbitrary value when reading a field, it does not
guarantee that a value written by one thread will be visible to another.
Thus, Synchronization has no effect unless both read and write operations
are synchronized.
In a multi-threaded environment, any mutable data visible to more than one
thread must be referenced within a synchronized block. This includes all
primitive data. All get and set methods for shared data which can change must
be synchronized.
It is a misconception that all primitives except long and double do not need
synchronized access.
5
The penalties for failing to synchronize shared mutable data are:
1) Safety failures2) Liveness failures
These failures are among the most difficult to debug.
Safety Hazards:Thread safety can be unexpectedly subtle because, in the absence of sufficient
synchronization, the ordering of operations in multiple threads is unpredictable and
sometimes surprising. Consider a class UnsafeSequence, which is supposed to generate a sequence of unique integer values.
public class UnsafeSequence { private int value;
/** Returns a unique value. */ public int getNext() { return value++; }}
6
The problem with UnsafeSequence is that with some unlucky timing, two threads could call
getNext and receive the same value as shown in the following figure:
Reason for above behaviour is:
The increment notation, nextValue++, denotes three separate operations: read the value,
add one to it, and write out the new value.
Since operations in multiple threads may be arbitrarily interleaved by the runtime,
it is possible for two threads to read the value at the same time, both see the same value,
and then both add one to it. The result is that the same sequence number is returned from
multiple calls in different threads.
7
Solution: For a multithreaded program's behavior to be predictable, access to
shared variables must be properly coordinated so that threads do not interfere
with one another. Fortunately, Java provides synchronization mechanisms to
coordinate such access.
UnsafeSequence can be fixed by making getNext a synchronized method, as
shown :public class Sequence {
private int nextValue;
public synchronized int getNext() {
return nextValue++;
}
}
8
Liveness Hazards:
A liveness failure occurs when an activity gets into a state such that it is permanently unable
to make forward progress. liveness failure that can occur in sequential programs is an inadvertent infinite loop,
where the code that follows the loop never gets executed.
The use of threads introduces additional liveness risks.
For example, if thread A is waiting for a resource that thread B holds exclusively, and B
never releases it, A will wait forever. This is called deadlock.
Other Liveness Hazards include starvation and livelock.
Starvation occurs when a thread is perpetually denied access to resources it needs in
order to make progress. Starvation in Java applications can be caused by inappropriate
use of thread priorities or by executing non-terminating constructs (infinite loops or
resource waits that do not terminate) with a lock held
9
Livelock is a form of liveness failure in which a thread, while not blocked, still cannot
make progress because it keeps retrying an operation that will always fail.
Livelock often occurs in transactional messaging applications, where the messaging
infrastructure rolls back a transaction if a message cannot be processed successfully,
and puts it back at the head of the queue. If a bug in the message handler for a
particular type of message causes it to fail, every time the message is dequeued and
passed to the buggy handler, the transaction is rolled back. Since the message is now
back at the head of the queue, the handler is called over and over with the same result.
(This is sometimes called the poison message problem.) The message handling
thread is not blocked, but it will never make progress either.
This form of livelock often comes from overeager error‐recovery code that mistakenly
treats an unrecoverable error as a recoverable one.
10
Volatile Variables
Shared variable, no re-ordering with other memory operationsNever cached in registers or CPU cachesRead always returns the most recent write by any threadUsed for:
Ensuring visibility of their own stateEnsuring visibility of the object they refer toIndicating an important lifecycle (init/shutdown) event
Completion, interruption, status flagUsed when:
Writes do not depend on current valueUnless a single thread only updates the variable
Doesn’t participate in invariants with other state variablesLocking is not required for any other reason while accessed
Locking = visibility + atomicityVolatile = visibility only!
Writes and reads of volatile fields have similar memory consistency effects as entering and exiting monitors, but do not entail mutual exclusion locking.
11
You can use volatile variables only when all the following criteria are met: Writes to the variable do not depend on its current value, or you can ensure
that only a single thread ever updates the value; The variable does not participate in invariants with other state variables; and Locking is not required for any other reason while the variable is being
accessed.
12
Do not oversynchronize!
Item 2
13
synchronize your class internally only if there is a good reason to do so, and document your decision clearly .
synchronize if one or more threads will access the same object or field.
do not synchronize an entire method if only parts of the method need to be synchronized
public void myMethod() {synchronize(this) {
// code that needs to be synchronized}// code that is already thread-safe
}
do not synchronize a method that uses only local variables: //a method which should not be synchronized
public int square(int n) {int s = n * n;return s;
}
14
Do as little as possible inside synchronized region. Obtain the lock, examine the shared data, transform the data as necessary, and drop the lock.
If a class could be used both in circumstances requiring synchronization and circumstances where synchronization not required, provide both the variants through wrapper class or subclass with synchronization.
inside a synchronized region, do not invoke a method that is designed to be
overridden, or one provided by a client in the form of a function object.
From the perspective of the class with the synchronized region, such
methods are alien. The class has no knowledge of what the method does and
has no control over it. Depending on what an alien method does, calling it from
a synchronized region can cause exceptions, deadlocks, or data corruption.
15
Prefer concurrency utilities over wait & notify
Item 3
16
Java 5.0 provides higher-level concurrency utilities that do the sorts of things
you formerly had to hand-code atop wait and notify. If you maintain code that uses wait and notify, make sure that it always invokes
wait from within a while loop using the standard idiom. The notifyAll method
should generally be used in preference to notify. If notify is used, great care
must be taken to ensure liveness.
Concurrency utilities are the classes which are designed to be used as building
blocks in building concurrent classes or applications. Using these to implement a
concurrent application can help you make your program clearer, shorter, faster,
more reliable, more scalable, easier to write, easier to read, and easier to
maintain. It fall into three categories: Executor Framework Concurrent collection Synchronizers
17
Executor framework
java.util.concurrent provides a flexible thread pool implementation as part of the
Executor framework. The primary abstraction for task execution in the Java class
libraries is not Thread, but Executor, as shown:
public interface Executor {
void execute(Runnable command);
} Executor may be a simple interface, but it forms the basis for a flexible and powerful
framework for asynchronous task execution that supports a wide variety of task
execution policies.
Executor is based on the producer‐consumer pattern, where activities that submit tasks
are the producers (producing units of work to be done) and the threads that execute
tasks are the consumers (consuming those units of work). Using an Executor is usually
the easiest path to implementing a producer‐consumer design in your application.
18
You can do many more things with an executor service. For example, you can wait for a particular task to complete you can wait for any or all of a collection of tasks to complete(using the invokeAny
or invokeAll methods), you can wait for the executor service’s graceful termination to complete (using the
awaitTermination method), you can retrieve the results of tasks one by one as they complete (using an
ExecutorCompletionService), and so on.
19
Concurrent Collections
Replacing synchronized collections with concurrent collections can offer dramatic
scalability improvements with little risk.
Synchronized collections achieve their thread safety by serializing all access to the
collection's state. The cost of this approach is poor concurrency; when multiple threads
contend for the collection‐wide lock, throughput suffers.
The concurrent collections, on the other hand, are designed for concurrent access from
multiple threads.
Java 5.0 adds ConcurrentHashMap, a replacement for synchronized hash‐based Map
implementations.
The synchronized collections classes hold a lock for the duration of each operation. Some
operations, such as HashMap.get or List.contains, may involve more work and thus can
take a long time, and during that time no other thread can access the collection.
20
ConcurrentHashMap is also a hash‐based, but it uses an entirely different locking strategy
that offers better concurrency and scalability. Instead of synchronizing every method on a common lock, restricting access to a
single thread at a time, it uses a finer‐grained locking mechanism called lock striping
to allow a greater degree of shared access.
It basically uses an array of 16 locks, each of which guards 1/16 of the hash buckets;
bucket N is guarded by lock N mod 16. this enables ConcurrentHashMap to support
up to 16 concurrent writers.
It allows many reading threads to access the map concurrently. Even readers can
access the map concurrently with writers, and a limited number of writers can modify
the map concurrently.
It provides iterators that do not throw ConcurrentModificationException, thus
eliminating the need to lock the collection during iteration.
21
Java 5.0 also adds BlockingQueue. It extends Queue to add blocking insertion and retrieval operations. If the queue is
empty, a retrieval blocks until an element is available, and if the queue is full (for
bounded queues) an insertion blocks until there is space available.
Blocking queues are extremely useful in producer‐consumer designs.
Java 5.0 also gives CopyOnWriteArrayList. It is a variant of ArrayList in which all write operations are implemented by making a
fresh copy of the entire underlying array. Because the internal array is never modified,
iteration requires no locking and is very fast.
It’s perfect for observer lists, which are rarely modified and often traversed.
22
Synchronizers
A synchronizer is any object that coordinates the control flow of threads based on its
state. Blocking queues can act as synchronizers; other types include semaphores, barriers,
and latches.
All synchronizers share certain structural properties: they encapsulate state that
determines whether threads arriving at the synchronizer should be allowed to pass or
forced to wait, provide methods to manipulate that state, and provide methods to wait
efficiently for the synchronizer to enter the desired state.
A latch is a synchronizer that can delay the progress of threads until it reaches its
terminal state. A latch acts as a gate: until the latch reaches the terminal state the gate
is closed and no thread can pass, and in the terminal state the gate opens, allowing all
threads to pass. Once the latch reaches the terminal state, it cannot change state again,
so it remains open forever.
23
Latches can be used to ensure that certain activities do not proceed until other one‐time
activities complete, such as: Ensuring that a computation does not proceed until resources it needs have been
initialized. A simple binary (two‐state) latch could be used to indicate "Resource R has
been initialized", and any activity that requires R would wait first on this latch.
Ensuring that a service does not start until other services on which it depends have
started. Each service would have an associated binary latch; starting service S would
involve first waiting on the latches for other services on which S depends, and then
releasing the S latch after startup completes so any services that depend on S can then
proceed.
Waiting until all the parties involved in an activity, for instance the players in a multi‐
player game, are ready to proceed. In this case, the latch reaches the terminal state after
all the players are ready.
24
CountDownLatch is a flexible latch implementation that can be used in any of
these situations; it allows one or more threads to wait for a set of events to
occur.
The latch state consists of a counter initialized to a positive number,
representing the number of events to wait for.
The countDown method decrements the counter, indicating that an event has
occurred, and the await methods wait for the counter to reach zero, which
happens when all the events have occurred.
If the counter is nonzero on entry, await blocks until the counter reaches zero,
the waiting thread is interrupted, or the wait times out.
25
Document thread safety
Item 4
26
How many times have you looked at the Javadoc for a class, and wondered, "Is
this class thread-safe?"
In the absence of clear documentation, readers may make bad assumptions
about a class's thread safety. they'll just assume it is thread-safe when it's not (that's really bad!), or maybe they'll assume that it can be made thread-safe by synchronizing on
the object before calling one of its methods (which may be correct, or may
simply be inefficient, or in the worst case, could provide only the illusion of
thread safety).
Thus, Write it down before you forget it (or leave the company). The best time to document thread safety is definitely when the class is first
written -- it is much easier to assess the thread safety requirements and
behavior of a class when you are writing it than when you (or someone else)
come back to it months later.
27
Thread Safety
A class is thread‐safe if it first must behave correctly in a single-threaded environment.
And further, it behaves correctly when accessed from multiple threads, regardless of the
scheduling or interleaving of the execution of those threads by the runtime environment,
and with no additional synchronization or other coordination on the part of the calling code.
To enable safe concurrent use, a class must clearly document what level of thread safety it
supports. Bloch has outlined a taxonomy that describes five categories of thread safety.
Though it does not cover all possible cases, it's a very good start.:
1) Immutable: Immutable objects are guaranteed to be thread-safe, and never require
additional synchronization. Because an immutable object's externally visible state
never changes, as long as it is constructed correctly, it can never be observed to be in
an inconsistent state.
Example: Most of the basic value classes in the Java class libraries, such as Integer,
String, and BigInteger, are immutable.
28
2) UnConditionally thread-safe : Instances of this class are mutable, but the
class has sufficient internal synchronization that its instances can be used
concurrently without the need for any external synchronization.
Examples: Random and ConcurrentHashMap classes.
3) Conditionally thread-safe classes are those for which each individual operation may be
thread-safe, but certain sequences of operations may require external synchronization.
Example: traversing an iterator returned from Hashtable or Vector classes.
The iterators returned assumes that the underlying collection will not be mutated while the
iterator traversal is in progress. To ensure that other threads will not mutate the collection
during traversal, the iterating thread should acquire exclusive access by synchronizing on a
lock. -- and the class's documentation should specify which lock that is (typically the object's
intrinsic monitor).
29
Conditionally thread-safe classes must document which method invocation sequences
require external synchronization, and which lock to acquire when executing these
sequences. If you write an unconditionally thread-safe class, consider using a private lock object in
place of synchronized methods. Because when a class commits to using a publicly
accessible lock, a client can mount a denial-of-service attack by holding the publicly
accessible lock for a prolonged period which can be accidental or intentional. The private lock object is inaccessible to clients of the class, it is impossible for them to
interfere with the object’s synchronization.
4) Not thread-safe: Instances of this class are mutable. To use them concurrently,
clients must surround each method invocation (or invocation sequence) with external
synchronization of the clients’ choosing.
Example: general-purpose collection implementations, such as ArrayList and HashMap.
30
5) Thread-hostile : Thread-hostile classes are those that cannot be rendered
safe to use concurrently, regardless of what external synchronization is
invoked. Thread hostility is rare, and typically arises when a class modifies
static data that can affect the behavior of other classes that may execute in
other threads.
An example of a thread-hostile class would be one that calls System.setOut().
Luckily, there are very few thread-hostile classes or methods in the Java libraries.
31
By documenting that a class is thread-safe (assuming it actually is thread-
safe), you perform two valuable services:
i. you inform maintainers of the class that they should not make
modifications or extensions that compromise its thread safety, and
ii. you inform users of the class that it can be used without external
synchronization. By documenting that a class is thread-compatible or conditionally thread-safe,
you inform users that the class can be used safely by multiple threads through
the appropriate use of synchronization. By documenting that a class is thread-hostile, you inform users that they
cannot use the class safely from multiple threads, even with external
synchronization. In each case, you are preventing potentially serious bugs, which would be
expensive to find and fix, before they happen.
32
Use lazy initialization judiciously
Item 5
33
Lazy initialization means that you do not initialize objects until the first time they are used.
It can be a useful performance-tuning technique. When you have thousands of objects that need complex initializations but only a few will
actually be used, lazy initialization provides a significant speedup to an application by
avoiding exercising code that may never be run. when there are many objects that need to be created and initialized, and most of these
objects will be used, but not immediately. In this case, it can be useful to spread out the
load of object initialization.
Lazy initialization has its uses. But in the presence of multiple threads, lazy initialization is
tricky. If two or more threads share a lazily initialized field, it is critical that some form of
synchronization be employed, or severe bugs can result. So Under most circumstances,
normal initialization is preferable to lazy initialization.
34
// Normal initialization of an instance field
private final Resource resource = new Resource();
// Lazy initialization of instance field - synchronized accessor@ThreadSafe
public class SafeLazyInitialization {
private static Resource resource;
public synchronized static Resource getInstance() {
if (resource == null)
resource = new Resource();
return resource;
}
}
35
If you must initialize a field lazily in order to achieve your performance goals, or to break a
harmful initialization circularity, then use the appropriate lazy initialization technique: Lazy initialization holder class idiom: It uses a class whose only purpose is to initialize
the Resource. The JVM defers initializing the ResourceHolder class until it is actually
used and because the Resource is initialized with a static initializer, no additional
synchronization is needed. The first call to getresource by any thread causes
ResourceHolder to be loaded and initialized, at which time the initialization of the
Resource happens through the static initializer.
@ThreadSafe
public class ResourceFactory {
private static class ResourceHolder {
public static Resource resource = new Resource();
}
public static Resource getResource() {
return ResourceHolder.resource ;
}
}
36
Double-checked Idiom :
It is the technique of choice for lazily initializing an instance field. It purported to offer the best of both worlds ‐ lazy initialization without paying the
synchronization penalty on the common code path. The way it worked was first to check whether initialization was needed without
synchronizing, and if the resource reference was not null, use it. Otherwise, synchronize
and check again if the Resource is initialized, ensuring that only one thread actually
initializes the shared Resource.
public class DoubleCheckedLocking {
private volatile Resource resource;
public Resource Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
37
The common code path ‐- fetching a reference to an already constructed Resource doesn't
use synchronization.
Single-check Idiom: It is a variant of the double-check idiom that dispenses with the second check. Used to lazily initialize an instance field that can tolerate repeated initialization.
// Single-check idiom - can cause repeated initialization!
private volatile Resource resource;
private Resource getInstance() {
Resource result = resource;
if (result == null)
resource = result = new Resource();
return result;
}
38
Don’t depend on the thread scheduler
Item 6
39
When many threads are runnable, the thread scheduler determines which ones get to
run, and for how long. Any reasonable operating system will try to make this
determination fairly, but the policy can vary. Therefore, well-written programs shouldn’t
depend on the details of this policy. Any program that relies on the thread scheduler
for correctness or performance is likely to be non-portable.
You must ensure that the average number of runnable threads is not significantly greater
than the number of processors. This leaves the thread scheduler with little choice: it
simply runs the runnable threads till they’re no longer runnable. The program’s behavior
doesn’t vary too much, even under radically different thread-scheduling policies.
Threads should not run if they aren’t doing useful work. The main technique for
keeping the number of runnable threads down is to have each thread do some useful
work and then wait for more.
40
When faced with a program that barely works because some threads aren’t getting
enough CPU time relative to others, do not rely on Thread.yield to “fix” the program
for it will make your program non-portable.
It may improve performance on one JVM implementation might make it worse on a
second and have no effect on a third. So a better options is to restructure the application
to reduce the number of concurrently runnable threads.
Thread priorities are among the least portable features of the Java platform.
Thread priorities may be used sparingly to improve the quality of service of an already
working program, but they should never be used to “fix” a program that barely works.
41
Avoid thread groups
Item 7
42
A thread group holds a collection of threads. For example, your program can use
ThreadGroup to group all printing threads into one group.
Thread groups were originally envisioned as a mechanism for isolating applets for
security purposes. They never really fulfilled this promise, and their security importance
has waned to the extent that they aren’t even mentioned in the standard work on the
Java security model.
Thread groups don’t provide much in the way of useful functionality, and much of the
functionality they do provide is flawed.
Thread groups are best viewed as an unsuccessful experiment, and you should simply
ignore their existence.
If you design a class that deals with logical groups of threads, you should probably use
thread pool executors