- Structuring classes => make them thread-safe, maintain them without accidentally undermining their safety guarantees.
- The design process for a thread-safe class:
- Identify the variables that form the object's state;
- An object's state := its fields.
- Identify the invariants that constrain the state variables;
- Establish a policy for managing concurrent access to the object's state.
- synchronization policy := what combination of immutability, thread confinement, locking used to maintain thread safety, which variables are guarded by which locks.
- => defines how an object coordinates access to its state without violating its invariants or postconditions.
- remember to document the synchronize policy.
- Identify the variables that form the object's state;
- Gathering Synchronization Requirements
- state space := the range of possible states.
- smaller state space => easier to reason.
- using final fields or immutable objects => simpler to analyze the state space.
- invariants and postconditions => additional synchronization or encapsulation requirements applied on state or state transitions.
- State-dependent Operations
- use existing library classes => wait for state-based preconditions.
- e.g., blocking queues, semaphores, synchronizers.
- use existing library classes => wait for state-based preconditions.
- State Ownership
- publish a mutable object => no longer have exclusive control => shared ownership.
- collection classes owns the state of the collection infrastructure, but client codes owns the objects stored in the collection.
- Instance confinement := encapsulating data within an object.
- => confines access to the data to the object's method => combining with locking discipline => ensure used in a thread-safe manner.
- => flexible choices of locking strategy => build thread-safe classes.
- Confined objects must not escape their intended scope.
- e.g.,
Collections.synchronizedList
wrapsArrayList
=> Decorator pattern => the wrapper object holds the only reachable reference to the underlying collection => thread-safe collection.
- e.g.,
- The Java Monitor Pattern
- An object encapsulates all its mutable state and guards it with the object's own intrinsic lock.
- Using a private lock => client code cannot acquire it.
- Example: Tracking Fleet Variables
- Even though
MutablePoint
is not thread-safe, the tracker class is.- Neigher the map nor any of the mutable points it contains is ever published.
- This copies mutable data before returning it to the client.
- Even though
- Composing classes out of objects that are thread-safe.
- e.g.,
CountingFactorizer
delegates its thread safety responsibilities to theAtomicLong
:CountingFactorizer
is thread-safe becauseAtomicLong
is.
- e.g.,
- Example: Vehicle Tracker Using Delegation
- a thread-safe
Map
implementationConcurrentHashMap
>HashMap
- an immutable
Point
>MutablePoint
- this implementation does not use any explicit synchronization.
- this version returns an unmodifiable but live view of the vehicle locatiosn instead of snapshot of the locations.
- if an unchanging view of the fleet is required,
getLoctaions
could instead return a shallow copy of thelocations
map.
- a thread-safe
- Independent State Variables
- if underlying state variables are independent => we can delagate thread safety to them.
CopyOnWriteArrayList
: a thread-safeList
implementation suited for managing listener lists.
- When Delegation Fails
- When additional constraints are imposed
- e.g., that the first number be less than or equal to the second.
- Both
setLower
andsetUpper
are check-then-act sequences => insufficient locking to make them atomic.
- When additional constraints are imposed
- Publishing Underlying State Variables
- If a state variable is thread-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.
- Example: Vehicle Tracker that Publishes Its State
- The contents of the
Map
are thread-safe mutable points rather than immutable ones. - The
getLocation
method returns an unmodifiable copy of the underlyingMap
=> callers can change the location of one of the vehicles by mutating theSafePoint
values in the returnedMap
. - Live nature may be a benefit or a drawback, depending on the requirements.
- The contents of the
- The Java class library provides useful "building block" classes that can be extended to add some operations.
- Use a different lock to guard its satte variables => change the synchronization policy => the subclass would silently break.
- Client-side Locking
- Extend the functionality of the class by placing extension code in a helper class.
- The problem with
ListHelper
is that it synchronizes on the wrong lock => whatever lock theList
uses to guard its state, it sure isn't the lock on theListHelper
. - client-side locking => put locking code for class C into classes that are totally unrelated to C => fragile though, couple the bahaviors of both the base class and the derived class.
- Composition
- => assume that once an object is passed to its constructor, the client will not use the underlying object directly again.
- => delegate operations to the underlying instance and add an atomic operation.
- => add an additional level of locking using its own intrinsic lock.
- Document a class's thread safety guarantees for its clients; document its synchonization policy for its maintainers.
- A synchronization policy documents:
- which variabales to make
volatile
- which variables to guard with locks
- which lock(s) guard which variables
- which variables to make immutable or confine to a thread
- which operations must be atomic
- which variabales to make
- Document:
- Implementation details for future maintainers.
- Publicly observable locking behavior of classes as specification.
- The thread safety guarantees made by a class.
- Interpreting Vague Documentation
- Guess from the perspective of implementor.