JAVA/Android Concurrency学习笔记

Thread Safety

Writing thread-safe code is, at its core, about managing access to state, and in particular to shared, mutable state.

An object’s state encompasses any data that can affect its externally visible behavior.

To protect data from uncontrolled concurrent access, synchronization to coordinate access to its mutable state should be used.

The primary mechanism for synchronization in Java is the synchronization keyword and the use of volatile variable, explicit locks and atomic variables.

**If multiple threads access the same mutable state variable without appropriate synchronization, your program is broken. There are 3 ways to fix it:
Don’t share the state variable across threads;
Make the state variable immutable;
Use synchronization whenever access the state variable;**

When designing thread-safe classes, good object-oriented techniques - encapsulation, immutability, and clear specification of invariants - are your best friends.

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.

Atomicity

This often happens among concurrent read-modify-write operations to the same variable.

Race condition
A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the runtime.
The most common type of race condition is check-then-act: you observe something to be true and then take action based on that observation; but in fact the observation could have become invalid between the time you observed it and the time you acted on it, causing a problem.

Race condition in Lazy Initialization
A common idiom that uses check-then-act is lazy initialization:
LazyInitRace has race conditions that can undermine its correctness:

public class LazyInitRace {\
    private ExpensiveObject instance = null;
    public ExpensiveObject getInstance() {
        if (instance == null)
            instance =  new ExpensiveObject();
        return instance;
    }
}

Compound Actions
To avoid race conditions, there must be a way to prevent other threads from using a variable while we’re in the middle of modifying it, so we can ensure that other threads can observe or modify the state only before we start or after we finish, but not in middle.

Operation A and B are atomic with respect to each other if, 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 reference.

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.

Locking

To preserve state consistency, update related state variables in a single atomic operation.

Intrinsic Locks
Java provide a built-in locking mechanism for enforcing atomicity: the synchronized block.

synchronized (lock) {
    //Access or modify shared state guarded by lock
}

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 block.
Intrinsic locks in Java act as mutexes, which means that at most one thread may own the lock.

Reentrancy
If a thread tries to acquire a lock that it alread holds, the request succeeds. Reentrancy means that locks are acquired on a per-thread rather than per-invocation basis.

Guarding State with Locks

For each mutable state variable that may be accessed by more than one threads, all accessses to that variable must be performed with the same lock held. In this case, we say that the variable is guarded by that lock.

Every shared, mutable variable should be guarded by exactly one lock. Make it clear to maintainers which lock that is.

For every invariant that involves more than one variable, all the variables involved in that invariant must be guarded by the same lock.

While synchronized methods can make individual operations atomic, additional locking is required - when multiple operations are combined into a compound action.

Liveness and Performance

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.

Sharing Objects

Synchronization has another significant, and subtle aspect: memory visibility. We want not only to prevent one thread from modifying the state of an object when another is using it, but also ensure that when a thread modifies the state of an object, other threads can actually see the changes that were made.

But without synchronization, this may not happen. You can ensure that ojects are published safely either by using explicit synchronization or by taking advantage of the synchronization built into library class.

Visibility

There is no guarantee that the reading thread will see a value written by another thread on a timely basis, or even at all.

In order to ensure visibility of memory writes across threads, you must use proper synchronization whenever data is shared across threads.

In the absence of synchronization, the compiler, processor, and runtime can do some downright weird things to the order in which operations appear to execute. Attempts to reason about the order in which memory actions “must” happen in insufficiently synchronized multi-threaded program will almost certainly be incorrect.

Stale Data
Not thread-safe, and susceptible to stale value: if one thread calls set, other threads calling get may or may not see that update.

public class MutableInteger {
    private int value;
    public int get() {return value;}
    public void set(int value) {this.value = value;}
}

Synchronizing only the setter would not be sufficient: threads calling get would still be able to see stale data.

public class MutableInteger {
    private int value;
    public synchronized int get() {return value;}
    public synchronized void set(int value) {this.value = value;}
}

Non-atomic 64-bit operations
Out-of-thin-air safety: when a thread reads a variable without synchronization, it may see a stale value, but at least it sees a value that was actually placed there by some thread rather than some random value.
For 64-bit numeric variable(double and long) that are not declared volatile: out-of-thin-air safety does not apply.
To declare them volatile or guarded with a lock.

Locking and Visibility
Locking is not just about mutable exclusion; it is also about memory visibility. To ensure that all threads see the most up-to-date values of shared mutable variables, the reading and writing thread must synchronize on a common lock.

Volatile variables
Volatile variables are not cached in registers or in caches where they are hidden from other processors, so a read of a volatile variable always return the most recent write by any thread.
Locking can guarantee both visibility and atomicity; volatile can only guarantee visibility;

**You can use volatile variable only when all the following criteria are met:
Writes to the variable do not depends 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;
Locking is not required for any other reason while the variable is being accessed;**

Publication and Escape

An object that is published when it should not have been is said to have escaped.

Safe Construction practice
Do not allow the this reference to escape during construction because an object is in a predictable, consistent state only after its constructor return.

A common mistake that can let the this reference escape during construction is to start a thread from a constructor. There is nothing wrong with creating a thread in a constructor, but it is best not to start the thread immediately. Instead, expose a start or initialize method that starts the owned thread. Calling an overridable instance method from the constructor can also allow the this reference to escape.

If you are temped to register an event listener or start a thread from a constructor, you can avoid the improper construction by using a private constructor and a public factory method.

public class SafeListener {
    private final EventListener listener;
    private SafeListener () {
       listener = new EventListener() {
           public void onEvent() {
               doSomething();
           }
       } ;
   }
   public static SafeListener newInstance(EventSource src) {
       SafeListener safe = new SafeListener;
       src.registerListener(safe.listener);
       return safe;
   }
}

Thread Confinement

Accessing shared, mutable data requires using synchronization; one way to avoid this requirement is to not share.
Thread confinement is an element of your program’s design that must be enforced by its implementation. The language and core libraries provide mechanism that can help maintaining thread confinement - local variables and the ThreadLocal class - but even with these, it is still the programmer’s responsibility to ensure that thread-confined objects do not escape from their intended thread.

Ad-hoc Thread Confinement
Ad-hoc thread confinement describes when the responsibility for maintaining thread confinement falls entirely on the implementation. Ad-hoc thread confinement can be fragile because none of the language features, such as visibility modifiers or local variables, helps confine the object to the target thread.
Eg, single-thread GUI system or read-modify-write operations on a shared volatile variable where only single thread writing the variable.

Stack Confinement
In stack confinement, an object can only be reached through local variables. Local variables are intrinsically confined to the executing thread; they exist on the executing thread’s stack, which is not accessible to other thread.
There is no way to obtain a reference to a primitive variable, so the language semantics ensure that primitive local variable are always stack confined.
Maintaining stack refinement for object references requires a little more assistance from the programmer to ensure that the reference does not escape.
Using a non-thread-safe object in a within-thread context is still thread-safe. However, be careful: if the assumption of within-thread usage is not clearly documented, future maintainers might mistakenly allow the object to escape.

ThreadLocal
ThreadLocal allows you to associate a per-thread value with a value-holding object. ThreadLocal provides get and set accessor methods that maintain a seperate copy of the value for each thread that use it, so a get returns the most recent value passed to set from the currently executing thread.
ThreadLocal variables are often used to prevent sharing in designs based on mutable Singletons or global variables.
ThreadLocal can also be used when a frequently used operation requires a temporary object sucah as a buffer and wants to avoid reallocating the temporary object on each invocation.
If you are porting a single-threaded application to a multithreaded environment, you can perserve thread safety by converting shared global variables into ThreadLocals, if the samantics of the shared global permits this.
It is easy to abuse ThreadLocal by treating its thread confinement property as a license to use global variables or as a mean of creating “hidden” method arguments: threadlocal variable can detract from usability and instroduce hidden coupling among classes.

Immutability

An immutable object is one whose state cannot be changed after construction. Immutable’s invariant are established by the constructor, and if their state cannot be changed, these invariants always hold.
Immutable objects are always thread-safe.
Immutability is not equivalent to simply declaring all fields of an object final. An object whose fields are all final may still be mutable, since final fields can hold references to mutable objects.
An object is immutable if: its state cannot be modified after construction; all its fields are final; and it is properly constructed(the this reference does not escape during construction);
Immutable objects can still use mutable objects internally to manage their state.

Final Field
It is the usage of final fields that makes possible the guarantee of initializaton safey that let immutable objects be freely accessed and shared without synchronization.
Just as it is a good practice to make all fields private unless they need greater visibility, it is a good practice to make all fields final unless they need to be mutable.

Using Volatile to Publish Immutable Objects
Race condition in accessing or updating multiple related variables can be eliminated by using an immutable object to hold all the variables. With a mutable holder oject, you would have to use locking to ensure atomicity; with an immutable object, once a thread acquires a reference to it, it need never worry about another thread modifying its state. If the variables are to be updated, a new holder object is created, but any threads working with the previous holder still see it in a consistent state.

Safe Publication

Sometimes we do want to share objects across threads, and in this case we must do so safely.

Improper Publication: When Good Object Go Bad
You cannot rely on the integrity of partially constructed objects. Some very strange things can happen when data is shared across threads without sufficient synchronization.

Immutable Objects and Initialization Safety
Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.

Safe Publication Idiom
To publish an object safely, both the reference to the object and the object’s state must be made visible to other threads at the same time. A properly constructed object can be safely published by: Initializing an object reference from a static initializer; Storing a reference to it into a volatile field or AtomicReference; Storing a reference to it into a final field of the properly constructed object; or Storing a reference to it into field that is properly guarded by a lock;
The internal synchronization in thread-safe collections means that placing an object in a thread-safe collection, such as a Vector or synchronizedList, fulfills the last of these requiements.
Using a static initializer is often the easist and safest way to publish objects that can be statically constructed:

pubic static Holder holder = new Holder(42);

Static initializers are executed by the JVM at class initialization time; because of internal synchronization in the JVM, this mechanism is guaranteed to safely publish any objects initialized in this way;

Effectively Immutable Objects
Objects that are not technically immutable, but whose state will not be modified after publication, are called effectively immutable.
Safely published effectively immutable objects can be used safely by any thread without additional synchronization;

Mutable Objects
If an object may be modified after construction, safe publication ensures only the visibility of the as-published state.To share mutable objects safely, they must be safely published and be either thread-safe or guarded by a lock.
The publication requirements for an object dependson its mutablility: Immutable objects can be published through any mechanism; Effectiviely immutable objects must be safely published; Mutable objects must be safely published and must be either thread-safe or guarded by a lock;

Sharing Objects Safely
The most useful policies for using and sharing objects in a concurrent program are: 1. Thread Confine. A thread-confined object is owned exclusively by and confined to the one thread and can be modified by its own thread. 2. Shared read-only. A shared read-only object can be accessed concurrently by multiple threads without additional synchronizaiton, but cannot be modified by any thread. Shared read-only objects include immutable and effectively immutable objects; 3. Shared thread-safe. A thread-safe object performs synchronization internally, so multiple threads can freely access it through its public interface without synchronization. 4. Guarded. A guarded object can be accessed only iwht a specific lock held. Guarded objects include those that are encapsulated within other thread-safe objects and published objects that are known to be guarded by a specific lock.

Composing Objects

This chapter covers patterns for structuring classes that can make it easier to make them thread-safe and to maintain them without accidentally undermining their safety guarantees.

Designing a Tread-safe Class

Encapsulation makes it possible to determine that a class is thread-safe without having to examine that the entire program.
The design process for a thread-safe class should include these three basic elements: Identify the variables that form the object’s state; Identify the variants that constrain the state variables; Establish a policy for managing concurrent access to the object’s state.
The synchronization policy defines how an object coordinates access to its state without violating its invariants or postconditions. It specifies what conbination of immutability, thread confinement, and locking is used to maintain thread safety, and which variables are guarded by which locks. To ensure that the class can be analyzed and maintained, document the synchronization policy.

Gathering Synchronization Requirements
You cannot ensure thread safety without understanding an object’s invariants and post-conditions. Constrains on the valid values or state transitions for state variables can create atomicity and encapsulation requirements.

State-dependent Operations
In a single-threaded program, if a precondition does not hold, the operation has no choice but to fail. But in a concurrent program, the precondition may become true later due to the action of another thread. Concurrent programs add the possibility of waiting until the precondition becomes true, and then proceeding with the operation.

State Ownership
When defining which variable form an object’s state, we want to consider only the data that object owns. Ownership is not embodied explicitly in the language, but is instead an element of class design.
In many cases, ownership and encapsulation go together - the object encapsulates the state it owns and owns the state it encapsulates. It is the owner of a given state variable that gets to decide on the locking protocol used to maintain the integrity of that variable’s state. Ownership implies control, but once you publish a reference to a mutable object, you no longer have exclusive control; at best, you might have “shared ownership”. A class usually does not own the objects passed to its methods or constructors, unless the method is designed to explicitly transfer ownership of objects passed in.

Instance Confinement

Encapsulating data within an object confines access to the data to the object’s methods, making it easier to ensure that the data is always accessed with the appropriate lock held.
Of course, it is still possible to violate confinement by publishing a supposedly confined object; if an object is intended to be confined to a specific scope, then letting it escape from that scope is a bug.
Confinement makes it easier to build thread-safe classes because a class that confines its state can be analyzed for thread safety without having to examine the whole program.

The Java Monitor Pattern
An object following the Java monitor pattern encapsulates all its mutable state and guards it with the object’s own intinsic lock.
There are advantages to using a private lock object instead of an object’s intrinsic lock(or any other publicly accessible lock). Making the lock object private encapsulates the lock so that client code cannot aquire it, whereas a publicly accessible lock allows client code to participate in its synchronization policy - correctly or incorrectly. Clients that improperly acquire another object’s lock could cause liveness problems, and verifying that a publicly accessible lock is properly used requires examing the entire program rather than a single class.

Delegating Thread Safety

All but the most trivial objects are composite objects. The java monitor pattern is useful when building classes from scratch or composing classes out of objects that are not thread-safe. But what if the compoents of our class are already thread-safe? Do we need to add an additional layer of thread safety? In some cases a composite made of thread-safe components is thread-safe, and in others it is merely a good start.

Independent State Variables
We can also delegate thread safety to more than one underlining state variable as long as those underlining state variables are independent, meaning that the composite class does not impose any invariants involving the multiple state variables.

When Delegation Fails
If a class has compound actions, delegation alone is again not a suitable approach for thread safety. In these cases, the class must provide its own locking to ensure that compound actions are atomic, unless the entire compound action can also be delegated to the underlying state variables.
If a class is composed of multiple independent thread-safe state variables and has no operations that have any invalid state transitions, then it can delegate thread safety to the underlying state variables.

Publishing Underlying State Variables

If a state variable is thead-safe, does not participate in any invariants that constrain its value, and has no prohibited state transitions for any of its operations, then it can safely be published.

Adding Functionality to Existing Thread-Safe Classes

Sometimes a thread-safe class that supports all of the operations we want already exists, but often the best we can find is a class that supports almost all the operations we want, and then we need to add a new operation to it without undermining its thead safety.
The safest way to add a new atomic operation is to modify the original class to support the desired operation, but this is not always possible because you may not have access to the source code or may not be free to modify it.
Another approach is to extend the class, assuming it was designed for extension. Extension is more fragile than adding code directly to a class, because the implementation of the synchronization policy is now distributed over multiple, seperately maintained source files.

Client-side Locking
If client code does not know the class returned from the wrapper, neither of these approaches - adding a method to the original class or extending the class works.
A third strategy is to extend the functionality of the class without extending the class itself by placing extension code in a “helper” class.
Client-side locking entails guarding client code that uses some object X with the lock X uses to guard its own state. In order to use client-side locking, you must know what lock X uses.

@ThreadSafe
public class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());
    public boolean putIfAbsent(E x) {
        synchronized (list) {
            boolean absent = !list.contains(x);
            if(absent)
                list.add(x);
            return absent;
        }
    }
}

Clent-side locking has a lot in common with class extension - they both couple the behavior of the derived class to the implementation of the base class. Just as extension violates encapsulation of implementation, client-side locking violates encapsulation of synchronization policy.

Composition
There is a less fragile alternative for adding an atomic operation to an existing class: composition. It adds an additional level of locking using its own instrinsic lock. It does not care whether the underlying class is thread-safe, because it provides its own consistent locking that provides thread safety even if the class is not thread-safe or changes its locking implementation.

Documentating Synchronization Policies

Document a class’s thread safety guarantees for its clients; document its synchronizaiton policy for its maintainer.
Each use of synchronized, volatile or any thread-safe class reflects a synchronization policy defining a strategy for ensuring the integrity of data in the face of concurrent access. That policy is an element of your program design, and the best time to document design decisions is at design time.

Building Blocks

This chapter covers the most useful concurrent building blocks, especially those introduced in Java 5.0 and Java 6.0, and some patterns for using them to structure concurrent applications.

Synchronization Collections

The synchronized collection classes include Vector and HashTable as well as their cousins, the synchronized wrapper classes created by the Collections.synchronizedXXX factory methods. These classes acheived thread safety by encapsulating their state and synchronizing every public method so that only one thread at a time can access the collection state.

Problems with Synchronized Collections
The synchronized collections are thread-safe, but you may sometimes need to use additional client-side locking to guard compound actions, which include iteration, navigation and conditional operations.
The synchronized collection classes guard each method with the lock on the synchronized collection object itself. By acquiring the collection lock we can make getLast and deleteLast atomic, ensuring that the size of the Vector does not change between calling size and get.
The problem of unreliable iteration can again be addressed by client-side locking, at some additional cost to scalability: By holding the Vector lock for the duration of iteration, we prevent other threads from modifying the Vector while we are iterating it.

synchronized (vector) {
    for(int i = 0; i < vector.size(); i++)
        doSomething(vector.get());
}

Iterators and Concurrentmodificationexception
The iterators returned by synchronized collections are not designed to deal with concurrent modification, and they are fail-fast - meaning that if they detect that the collection has changed since iteration began, they throw the unchecked ConcurrentModificationException.

List<Widget> widgetList = Collections.synchronizedList(new ArrayList<Widget>());
...
//May throw ConcurrentModificationException
for(Widget w : widgetList)
    doSomething(w);

There are several reasons, however, why locking a collection during iteration may be undesirable. Other threads that need to access the collection will block until the iteration is completed. If the collection is locked, doSomething is being called with a lock held, which is a risk factor for deadlock. Even in the absence of starvation or deadlock risk, locking collections for significant periods of time hurts application scalability.
An alternative to locking the collection during iteration is to clone the collection and iterate the copy instead.

Hidden Iterators
Iteration could be invoked by the collection’s toString, hashCode, equals, containsAll, removeAll, retainAll methods, as well as the constructors that take collections as arguments.
Just as encapsulating an object’s state makes it easier to prevent its invariants, encapsulating its synchronization makes it easier to enforce its synchronization policy.

Concurrent Collections

The concurrent collections are designed for concurrent access from multiple threads.
Replacing synchronized collections with concurrent collections can offer dramatic scalability improvements with little risks.

ConcurrentHashMap
ConcurrentHashMap uses a finer-grained locking mechanism called lock stripping to allow a greater degree of shared access. Arbitrarily many reading threads can access the map concurrently, readers can access the map concurrently with writers, and a limited number of writers can modify the map concurrently. The result is far higher throughput under concurrent access, with little performance penalty for single-threaded access.
The iterators returned by ConcurrentHashMap are weakly consistent instead of fail-fast. A weakly consistent iterator can tolerate concurrent modification, traverses elements as they existed when the iterator was constructed, and my(but is not guaranteed to) reflect modifications to the collection after the construction of the iterator.
As with all improvement, there are still a tradeoff: the semantics of methods that operate on the entire map, such as size and isEmpty, have been slightly weakened to reflect the concurrent nature of the collection.
The one feature offered by the synchronized Map implementations but not by ConcurrentHashMap is the ability to lock the map for exclusive access.

Additional Atomic Map Operations
Since a ConcurrentHashMap cannot be locked for exclusive access, we cannot use client-side locking to create atomic operations such as put-if-absent, . Instead, a number of common compound operations such as put-if-absent, remove-if-equal,. replace-if-equal are implemented as atomic operations and specified by the ConcurrentHashMap interface.

public interface ConcurrentMap<K, V> extends Map<K, V> {
    //insert into map only if no value is mapped from K
    V putIfAbsent(K key, V value);

   //Remove only if K is mapped to V
   boolean remove(K key, V value);

   //Replace value only if K is mapped to oldvalue
   boolean replace(K key, V oldValue, V newValue);

   //Replace value only if K is mapped to some value
   V replace(K key, V newValue);
}

CopyOnWriteArrayList and CopyOnWriteSet
The copy-on-write collections derive their thread safety from the fact that as long as an effectively immutable object is properly published, no further synchronization is required when accessing it. They implement mutability by creating and republishing a new copy of the collection every time it is modified. The iterators returned by the copy-on-write collections do not throw ConcurrentModificaitonException and return the elements exactly as they were at the time the iterator was created, regardness of subsequent modifications.
The copy-on-write collections are reasonable to use only when iteration is far more common than modificaiton because there is some cost to copying the backing array every time the collection is modified, especially if the collection is large. This criterion exactly describes may event-notification systems.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值