天天看點

Java Concurrency In Practice -Chapter 2 Thread Safety

Writing thread-safe code is managing access to

state and in particular to shared, mutable state.

Object‘s state is its data, stored in state

variables such as instance or static fields.

Whether an object needs to be thread-safe depends

on whether it will be accessed from multiple threads.

Synchronization includes

Synchronized keyword

Volatile variables

Explicit locks

Atomic variables  

Ways to avoid inappropriate synchronization with

multiple threads access.

Don‘t share the state variable across threads;

Make the state variable immutable; or

Use synchronization whenever accessing the

state variable.  

Good practice on designing thread-safe classes

Good object-oriented techniques -

encapsulation(Don‘t expose the accessibility of the fields of the class too

much).

Immutability

Clear specification of

invariants  

2.1 What is Thread Safety

A class is thread-safe if 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.  

Thread safe classes encapsulate any needed

synchronization so that clients need not provide their own.

Stateless objects are always

thread-safe.

eg. It is only when servlets want to remember

things from one request to another that the thread safety requirement becomes an

issue.

2.2 Atomicity

Classic operation scenario

read-modify-write: To

increment a counter, you have to know its previous value and make sure no one

else changes or uses that value while you are in mid‐update.

check‐then‐act: you observe

something to be true and then take action based on that observation (create

X); but in fact the observation could have become invalid between the time you

observed it and the time you acted on it (someone else created X in the

meantime), causing a problem (unexpected exception, overwritten data, file

corruption).

2.2.1 Race Conditions

A race condition occurs when the correctness of a

computation depends on the relative timing or interleaving of multiple threads

by the runtime.

Operations A and B are atomic with respect to

each other, from the perspective of a thread executing A, when another thread

executes B, either all of B has executed or none of it has. An atomic operation

is one that is atomic with respect to all operations, including itself, that

operate on the same state.  

The java.util.concurrent.atomic

package contains atomic variable classes for effecting atomic state transitions

on

numbers and object references. By

replacing the long counter with an AtomicLong, we

ensure that all actions that

access the counter state are

atomic.  

Principle

Where practical, use existing thread-safe

objects, like AtomicLong, to manage your class‘s state. It is simpler to reason

about the possible states and state transitions for existing thread-safe objects

than it is for arbitrary state variables, and this makes it easier to maintain

and verify thread safety.

2.3 Locking

Dealing with more than one states currency

operations within one object.

To preserve state consistency, update related

state variables in a single atomic

operation.  

2.3.1 Intrinsic Locks

A synchronized block has two parts: a reference

to an object that will serve as the lock, and a block of code to be guarded by

that lock. A synchronized method is shorthand for a synchronized block that

spans an entire method body, and whose lock is the object on which the method is

being invoked. (Static synchronized methods use the Class object for the

lock.)  

2.3.2 Reentrancy

Reentrancy means that locks are acquired on a

per-thread rather than per-invocation basis.

When a thread acquires a previously unheld lock,

the JVM records the owner and sets the acquisition count to one. If that same

thread acquires the lock again, the count is incremented, and when the owning

thread exits the synchronized block, the count is decremented. When the count

reaches zero, the lock is released.  

2.4 Guarding State with Locks

For each mutable state variable that may be

accessed by more than one thread, all accesses to that variable must be

performed with the same lock held.

For every invariant that involves more than one

variable, all the variables involved in that invariant must be guarded by the

same lock.

put-if-absent operation

2.5 Liveness and Performance

Java Concurrency In Practice -Chapter 2 Thread Safety

Refined solution for concurrent operation

on more than on state object inside the Class instance

There is frequently a tension between

simplicity and performance. When implementing a synchronization policy, resist

the temptation to prematurely sacrifice simplicity (potentially compromising

safety) for the sake of performance.

Avoid holding locks during lengthy

computations or operations at risk of not completing quickly such as network

or console I/O.