color
Application-level concurrency: Optimistic Concurrency Control
Summary of Friday’s lecture We learned about:
• Representing tasks that return values using implementations ofCallable.
• Accessing returned values and controlling task execution usingFutures.
• FutureTasks and their implementations.
• RunningFutureTasks.
We have seen how
• Creating and destroying threads is expensive.
• Thread poolsreduce this overhead by reusing existing threads.
• ExecutorServiceis a class that manages thread pools.
• Any Callableor Runnable task can be submitted to an Executor or a ExecutorService.
Next step, optimistic concurrency control!
Compare-and-set
• Typical computer architectures offer atomicmachine code instructions specifically addressed at managing concurrency.
• One such instruction is compare-and-set (also compare-and-swap or CAS):
1. atomicallycompares the value at a memory address to a given ex- pected value.
2. If these are equivalent, sets the value at the memory address to an updated value.
• These hardware instructions can be used to efficiently implement synchro- nization primitives, such as locks.
• These instructions can also be used to develop optimisticor lock-free algorithms.
• In some low-contention scenarios, these run faster than lock-based solu-
Compare-and-set
Conceptually, an integer compare-and-set instruction is equivalent to the following method:
public boolean synchronized compare_and_set(
int* address, int expect, int update) { boolean asExpected = (*address == expect);
if (asExpected)
*address = update;
return asExpected;
}
Here “*” is C-like notation indicating a pointer dereference:
• addressis a memory location (pointer).
• *addressis the contents of that memory location.
Compare-and-set
• The CAS instruction involvesfourdistinct elements:
compare_and_set(int* address, int expect, int update)
• Actual instruction name differs between architectures. For x86 machines the instruction isCMPXCHG(compare and exchange).
Locking using compare-and-set
The atomicity of the CAS operation can be used to create a simple lock as follows:
• Suppose the value of*addressis an integer representing the thread which holds the lock, with zero meaning that no thread has acquired the lock.
• The following operation will acquire the lock for thread 1, provided the lock is free:
boolean success = compare_and_set(address, 0, 1);
• If the lock is already held by thread 2, the CAS will fail and returnfalse.
• Otherwise, the CAS will succeed, meaning that contents of*addresswill be set to 1 and the CAS will return true.
Locking using compare-and-set
• The following pseudo-code uses CAS to implement a lock acquisition at- tempt that blocks the execution of the thread 1 until the lock is acquired.
while (!compare_and_set(address, 0, 1));
• This is reminiscent of the way in which reentrantLock.lock() blocks execution untilreentrantLockis able to be acquired.
• This particular design is known as aspin lock (it keeps “spinning” until the lock is acquired).
The atomic package in Java
The Java packagejava.util.concurrent.atomic offers a set of “atomic”
classes:
• These wrap around a variety of Java types.
• They ensure thread-safe lock-free access.
• They use efficient hardware compare-and-set instructions.
The atomic package in Java
Excerpts from thejava.util.concurrent.atomicclasses:
• AtomicInteger(wraps an integer)
• AtomicLong(wraps a long integer)
• AtomicReference(wraps an object reference - i.e. an object pointer)
• AtomicStampedReference(wraps a pair consisting of an object reference plus an integer stamp)
AtomicInteger methods
AtomicInteger has all of the thread-safe methods required by our thread-safe counter, and more. In particular:
boolean compareAndSet(int expect, int update)
• Atomically sets the value to the given updated value if (and only if) the current value is the same as the expected value.
• herevalueis the value of the private integer wrapped by thisAtomicInteger.
• This method istypicallybased on a hardware compare-and-set instruction, so it is thread-safe and lock-free.
AtomicInteger methods The call
atomicInt.compareAndSet(expect, update);
whereatomicIntis an instance of AtomicInteger is equivalent to the earlier pseudo-code
compare_and_set(address_of(value), expect, update)
where we assume that address of(value)returns the memory address of the integer value wrapped by thisAtomicInteger.
AtomicInteger pseudo-code
The as far as itscompareAndSet()method is concerned, theAtomicInteger class might be described by the following pseudo-code:
public class AtomicInteger { int value;
public boolean synchronized compareAndSet(
int expect, int update) {
boolean asExpected = (this.value == expect);
if (asExpected)
this.value = update;
return asExpected;
}
Other AtomicInteger methods
These methods are also thread-safe and lock-free:
• int get(): Gets the currentvalue.
• int decrementAndGet(): Atomically decrements by one then retrieves the currentvalue.
• int incrementAndGet(): Atomically increments by one then retrieves the currentvalue.
• int getAndDecrement(): Atomically retrieves and then decrements by one the currentvalue.
• int getAndIncrement(): Atomically retrieves and then increments by one the currentvalue.
Note
• Methods starting withgetreturn the valuebeforethe update
• Methods ending withgetreturn the valueafter the update
Recap: Counter class
Consider again the followingCounterclass.
public class Counter { protected int c = 0;
public void increment() { c++;
}
public void decrement() { c--;
}
public int getValue() { return c; } }
Counter.java
• Can we make this thread-safe without synchronization/locking?
Lock-free thread-safe Counter class Yes we can!
import java.util.concurrent.atomic.AtomicInteger;
public class CounterLockFree extends Counter {
protected AtomicInteger cAtomic = new AtomicInteger(0);
public void increment() { cAtomic.incrementAndGet();
}
public void decrement() { cAtomic.decrementAndGet();
}
public int getValue() { return cAtomic.get();
AtomicInteger implementations
We have learned thatcompareAndSet()is based on the hardware compare- and-set (CAS) instruction.
What about the other methods, such asincrementAndGet()?
There are several possible implementations:
• based on compareAndSet
• based on other atomic hardware instructions (if available) Example: incrementAndGet implementation
This is a typical optimistic lock-free approach:
public final int incrementAndGet() { while (true) {
int current = this.value;
int next = current + 1;
boolean ok = compareAndSet(current, next); // Note 1
if (ok)
return next;
// Note 2 }
}
Note 1: IfAtomicInteger’s privatevalue has not been changed (by another thread) then this is updated to the next value andokis set totrue.
Note 2: Otherwise, we retry the procedure until success.
AtomicInteger implementations
• Alternatively, the methodincrementAndGetcan be based on the hardware lock prefixavailable for some instructions on modern Intel processors.
• A few instructions which, if modified with a lock prefix, run atomically on Intel processors include: ADD, SUB, INC, DEC.
• When modified with the lock prefix, these instructions are typically re- named LOCK ADD, LOCK SUB, LOCK INC, LOCK DEC.
The ABA problem Related Question
Why do we need theAtomicStampedReferenceclass?
Answer
If it finds the expected value, compare-and-set cannot determine whether
• The value referenced by the memory address has never been changed (was A and it is still the same A), or
• the value actually has been changed, but subsequently restored to its pre- vious value (was A, changed to B, then back to A).
This is known asthe ABA problem.
An ABA scenario
AtomicInteger X = new AtomicInteger(10);
Thread 1
boolean a = X.compareAndSet(10, 20);
// ...
// ...
boolean b = X.compareAndSet(20, 30);
Thread 2 // ...
boolean a = X.compareAndSet(20, 40);
boolean b = X.compareAndSet(40, 20);
// ...
With “bad” interleaving, allcompareAndSet()calls succeed andXtakes the values 10,20,40,20,30 successively.
The ABA problem
Stamped references can be used to solve the ABA problem:
• By atomically associating a “stamp” number to each reference.
• Stamp numbers should be monotonically incremented at each update.
• Thus, even if a reference looks unchanged, its stamp number will be dif- ferent if the reference was updated.
• In this case, the compareAndSet() method will fail, as the comparison
The ABA problem: concrete example
Consider the following basic LIFO stack implementation:
public classStack {
public AtomicReference<Item> top=newAtomicReference<Item>();
public void push(Item newTop) { do{
newTop.prev =top.get();
}while(!top.compareAndSet(newTop.prev,newTop));
}
public Item pop() { Item oldTop,newTop;
do{
oldTop =top.get();
newTop =oldTop.prev;
}while(!top.compareAndSet(oldTop,newTop));
oldTop.prev =null;
returnoldTop;
} }
Stack.java
• Is this implementation thread-safe?
The ABA problem: example
The ABA problem: example solution
public classStackStamped {
public AtomicStampedReference<Item>top =
newAtomicStampedReference<Item>(null, 0);
public int[] stampHolder=new int[1];
// ... push() ...
public Item pop() { Item oldTop,newTop;
intexpectedStamp,newStamp;
do{
oldTop =top.get(stampHolder);
newTop =oldTop.prev;
expectedStamp =stampHolder[0];
newStamp =expectedStamp + 1;
}while(!top.compareAndSet(oldTop,newTop,expectedStamp, newStamp));
oldTop.prev =null;
returnoldTop;
} }
StackStamped.java
Solved usingAtomicStampedReference!
Summary
Today we have:
• Learned how to define an optimistic concurrency control strategy.
• Discussed where such strategies are useful.
• Learned about the compare-and-set (or compare-and-swap or CAS) and how to describe its operation using pseudo-code.
• Learned what theAtomicclasses in Java’s atomic package do, where they can be used.
• Talked about the ABA problem and seen examples of where it can arise.
• Discussed the use of stamped references to address this problem.