Concurrency

42
Concurrency [Effective Java by Joshua Bloch ]

description

 

Transcript of Concurrency

Page 1: Concurrency

Concurrency[Effective Java by Joshua Bloch]

Page 2: Concurrency

2

Synchronize access to shared mutable data

Item 1

Page 3: Concurrency

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

Page 4: Concurrency

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.

Page 5: Concurrency

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++; }}

Page 6: Concurrency

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.

Page 7: Concurrency

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++;

}

}

Page 8: Concurrency

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

Page 9: Concurrency

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.

Page 10: Concurrency

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.

Page 11: Concurrency

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.

Page 12: Concurrency

12

Do not oversynchronize!

Item 2

Page 13: Concurrency

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;

}

Page 14: Concurrency

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.

Page 15: Concurrency

15

Prefer concurrency utilities over wait & notify

Item 3

Page 16: Concurrency

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

Page 17: Concurrency

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.

Page 18: Concurrency

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.

Page 19: Concurrency

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.

Page 20: Concurrency

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.

Page 21: Concurrency

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.

Page 22: Concurrency

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.

Page 23: Concurrency

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.

Page 24: Concurrency

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.

Page 25: Concurrency

25

Document thread safety

Item 4

Page 26: Concurrency

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.

Page 27: Concurrency

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.

Page 28: Concurrency

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).

Page 29: Concurrency

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.

Page 30: Concurrency

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.

Page 31: Concurrency

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.

Page 32: Concurrency

32

Use lazy initialization judiciously

Item 5

Page 33: Concurrency

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.

Page 34: Concurrency

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;

}

}

Page 35: Concurrency

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 ;

}

}

Page 36: Concurrency

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;

}

}

Page 37: Concurrency

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;

}

Page 38: Concurrency

38

Don’t depend on the thread scheduler

Item 6

Page 39: Concurrency

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.

Page 40: Concurrency

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.

Page 41: Concurrency

41

Avoid thread groups

Item 7

Page 42: Concurrency

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