Nothing bad ever happens. Race conditions are examples of unsafe programms.
Something good eventually happens. A liveness failure occurs when an activity gets ino a state such that it is permanently unable to make forward
progress. Deadlock, starvation and livelocks are examples.
In well designed concurrent applications the use of threads is a net performance gain, but threads nevertheless carry some degree of runtime overhead:
Context switches.
If multiple threads access the same mutable state variable without synchronization, your programm is broken. There are
three ways to fix it:
A class is thread-safe if it behaves correctly (= conform to it's specification) when accessed from multiple threads,
regardless of the scheduling or interleaving of the execution of those threads vy the runtime environment, and with no additional synchronization
or other coordination on the part of the calling code.
Stateless objects are always thread-safe (like most servlets)
Race conditition: A race condition occurs when the correctness of a computation depends on the relative timing or interleaving of multiple threads by the runtime; in other words, when getting the right answer relies on lucky timing. The most commun type of race-condition, is check-then-act, where a potentially stale observation
is used to make a decision on what to do next.
long a; a++ → not atomic, it's a read-modify-write-opeartion
check-then-act or read-modify-write are also called compoud actions. The must be executed atomically in order to be thread-safe.
When a single element of state is added to a stateless class, the resulting class will be thread-safe if the state is entirely managed by a thread-safe object.
This is not the case anymore when moving from one shared element of state to two or more dependant elements:
To preserve state consistency, update related state variables in a single atomic operation.
Reetrancy means that locks are acquired bon a per-thread rather than per-invocation basis.
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.
While synchronized methods can make individual operations atomic, additional locking is required when multiple operations are combined into a compound action, for example put-if-absent:
if(!vector.contains(element)) vector.add(element)
Avoid holding locks during lengthy computations or operations at risk of not completing quicky such as network or console I/O.
In general, there is no guarantee that the reading thread will see a value written by another thread on a timely basis, or even at all (stale data). In order to ensure visibility of memory writes across threads, you must use synchronization.
Out-of-thin-aire 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. This is true for all variables, except 64-bit long and double, which are not volatile. The JVM is permitted to treat 64-bit read or write as two seperate 32-bit operations which are not atomic.
Locking is not just about mutual exclustion, it is also about memory visibility.
Everything thread A did in or prior to a synchronized block is visible to thread B when it executes a synchronized block guarded by the same lock.
When tread A writes to a volatile variale and subsequently thread B reads that same variable, the values of all variables that were visible to A prior writing to the volatile variable become visible to B after reading the volatile variable. So from a memory visibility perspective, writing a volatile variable is like exiting a sychronized block and reading a volatile variable is like entering a synchronized block.
The most common use for volatile variables is as a completion, interruption, or status flag.
Locking can guarantee both visibility and atomicity; volatile variables can only guarantee visibility. It is safe to perform read-modify-write operations on shared volatile variables as long as you ensure that the volatile variable is only written from a single thread.
Use volatile varaibles only when the following is met:
Do not allow the this reference to escape during construction. Which means that you should not register an event listener or start a thread from a constructor.
Use a private constructor and a public factory method, if you have to do it.
If data iss only accessed from a single thread, no synchronization is needed. This technique, thread confinement, is one of the simplest ways to achieve thread safety.
When an object is confined to a thread such usage is automatically thread-safe even if the confined object itself is not.
Example: A JDBC-Connection is thread-safe, because multiple requests asks for a connection. The connection itself is not, because it is used only by a single thread.
Describes the responsibility for maintaining thread confinement falls entireley on the implementation. Ad-hoc thread confinement can be fragile because
none of the languages features, such as visibility modifiers or local variables, helps confine the object to the target thread.
Is a special cse of thread confinement in which an object can only be reached through local variables. Local variables are intrinsically confined to the executing thread;
they exist on the executing thred's stack, which is not accessible to other threads.
Thread-Local provides get and set accessor methods that maintain a separate copy of the value for each thread that uses it, so a get returns the most recent value passed to set from the currenty executing thread. Conceptually, you can think of a ThreadLocal<T> as holding a Map<Thread,T>, that stores the thread-specific values, though this is not how it is actually implemented. The thread-specific values are stored in the Thread itself.
Immutable objects are always thread-sasfe.
An object is immutable if:
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.
The combination of an immutable holder object for multiple state variables related by an invariant, and a volatile reference used to ensure its timely visibility, can
be used to ensure thread safety.
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:
The publication requirements for an object depend on its mutability:
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 invariants that constrain the state variables * Establish a policy for managing concurrent access to the object's state.
PersonSet illustrates how confinement and locking can work together to make a class thread-safe even thwn its component state variables are not. The state of PersonSet
is managed by a HashSet, which is not thread-safe. But because mySet is private and not allowed to escape, the HashSet is confined to the PersonSet. The only code paths that can access mySet are addPerson and containsPerson, and each of these acquired the lock on the PersonSet. All its state is guarded by its intrinsic lock, making
PersonSet thread-safe.
@ThredSafe
public class PersonSet {
@GuardedBy("this)
private final Set<Person> mySet = new HashSet<Person>();
public synchronized void addPerson(Person p) {
mySet.add(p);
}
public synchronized boolean containsPerson(Person p) {
return mySet.contains(p);
}
}
The example above encapsulates all its mutable state and guards it with the object's own instrinsic lock. This is called Java Monitor pattern It is used by many library classes, such as Vector and Hashtable.
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 unterlying state variable.
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.