AtomicStampedReference
- Creating an AtomicStampedReference
- Getting the AtomicStampedReference Reference
- Getting the AtomicStampedReference Stamp
- Getting Reference and Stamp Atomically
- Setting the AtomicStampedReference Reference
- Comparing and Setting the AtomicStampedReference Reference
- AtomicStampedReference and the A-B-A Problem
Jakob Jenkov |
The AtomicStampedReference class provides an object reference variable which can be read and written atomically.
By atomic is meant that multiple threads attempting to change the same AtomicStampedReference will not make
the AtomicStampedReference end up in an inconsistent state.
The AtomicStampedReference is different from the AtomicReference
in that the AtomicStampedReference keeps both an object reference and a stamp internally. The
reference and stamp can be swapped using a single atomic compare-and-swap operation, via the compareAndSet()
method.
The AtomicStampedReference is designed to be able to solve the A-B-A problem which is not possible
to solve with an AtomicReference alone. The A-B-A problem is explained later in this text.
Creating an AtomicStampedReference
You can create an AtomicStampedReference instance like this:
Object initialRef = null;
int initialStamp = 0;
AtomicStampedReference atomicStampedReference =
new AtomicStampedReference(intialRef, initialStamp);
Creating a Typed AtomicStampedReference
You can use Java generics to create a typed AtomicStampedReference. Here is a typed
AtomicStampedReference example:
String initialRef = null;
int initialStamp = 0;
AtomicStampedReference<String> atomicStampedReference =
new AtomicStampedReference<String>(
initialRef, initialStamp
);
The Java AtomicStampedReference created in this example will only accept references to String instances. It is recommended to always use a generic type with an AtomicStampedReference, if you know the type of the reference you will keep in it.
Getting the AtomicStampedReference Reference
You can get the reference stored in an AtomicStampedReference using the AtomicStampedReference's
getReference() method. If you have an untyped AtomicStampedReference then the getReference() method
returns an Object reference. If you have a typed AtomicStampedReference then getReference() returns
a reference to the type you declared on the AtomicStampedReference variable when you created it.
Here is first an untyped AtomicStampedReference getReference() example:
String initialRef = "first text"; AtomicStampedReference atomicStampedReference =
new AtomicStampedReference(initialRef, 0); String reference = (String)
atomicStampedReference.getReference();
Notice how it is necessary to cast the reference returned by getReference() to a String because
getReference() returns an Object reference when the AtomicStampedReference is untyped.
Here is a typed AtomicStampedReference example:
String initialRef = "first text";
AtomicStampedReference<String> atomicStampedReference =
new AtomicStampedReference<String>(
initialRef, 0
);
String reference =
atomicStampedReference.getReference();
Notice how it is no longer necessary to cast the referenced returned by getReference() because the compiler
knows it will return a String reference.
Getting the AtomicStampedReference Stamp
The AtomicStampedReference also contains a getStamp() method which can be used to
obtain the internally stored stamp. Here is a getStamp() example:
String initialRef = "first text";
AtomicStampedReference<String> atomicStampedReference =
new AtomicStampedReference<>(initialRef, 0);
int stamp = atomicStampedReference.getStamp();
Getting Reference and Stamp Atomically
You can obtain both reference and stamp from an AtomicStampedReference in a single, atomic operation
using the get() method. The get() method returns the reference as return value from
the method. The stamp is inserted into an int[] array that is passed as parameter to the get()
method. Here is a get() example:
String initialRef = "text";
String initialStamp = 0;
AtomicStampedReference<String> atomicStampedReference =
new AtomicStampedReference<>(
initialRef, initialStamp
);
int[] stampHolder = new int[1];
String ref = atomicStampedReference.get(stampHolder);
System.out.println("ref = " + ref);
System.out.println("stamp = " + stampHolder[0]);
Being able to obtain both reference and stamp as a single atomic operation is important for some types of concurrent algorithms.
Setting the AtomicStampedReference Reference
You can set the reference stored in an AtomicStampedReference instance using its set() method.
In an untyped AtomicStampedReference instance the set() method takes an Object
reference as first parameter. In a typed AtomicStampedReference the set() method takes whatever
type as parameter you declared as its type when you declared the AtomicStampedReference.
Here is an AtomicStampedReference set() example:
AtomicStampedReference<String> atomicStampedReference =
new AtomicStampedReference<>(null, 0);
String newRef = "New object referenced";
int newStamp = 1;
atomicStampedReference.set(newRef, newStamp);
There is no difference to see in the use of the set() method for an untyped or typed reference.
The only real difference you will experience is that the compiler will restrict the types you can set on a typed
AtomicStampedReference.
Comparing and Setting the AtomicStampedReference Reference
The AtomicStampedReference class contains a useful method named compareAndSet(). The
compareAndSet() method can compare the reference stored in the AtomicStampedReference instance
with an expected reference, and the stored stamp with an expected stamp, and if they two references and stamps are
the same (not equal as in equals() but same as in ==), then a new reference can be
set on the AtomicStampedReference instance.
If compareAndSet() sets a new reference in the AtomicStampedReference the compareAndSet()
method returns true. Otherwise compareAndSet() returns false.
Here is an AtomicStampedReference compareAndSet() example:
String initialRef = "initial value referenced";
int initialStamp = 0;
AtomicStampedReference<String> atomicStringReference =
new AtomicStampedReference<String>(
initialRef, initialStamp
);
String newRef = "new value referenced";
int newStamp = initialStamp + 1;
boolean exchanged = atomicStringReference
.compareAndSet(
initialRef, newRef,
initialStamp, newStamp);
System.out.println("exchanged: " + exchanged); //true
exchanged = atomicStringReference
.compareAndSet(
initialRef, "new string",
newStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged); //false
exchanged = atomicStringReference
.compareAndSet(
newRef, "new string",
initialStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged); //false
exchanged = atomicStringReference
.compareAndSet(
newRef, "new string",
newStamp, newStamp + 1);
System.out.println("exchanged: " + exchanged); //true
This example first creates an AtomicStampedReference and then uses compareAndSet()
to swap the reference and stamp.
After the first compareAndSet() call the example attempts to swap the reference and stamp
two times without success. The first time the initialRef is passed as expected reference, but the
internally stored reference is newRef at this time, so the compareAndSet() call
fails. The second time the initialStamp is passed as the expected stamp, but the internally stored
stamp is newStamp at this time, so the compareAndSet() call fails.
The final compareAndSet() call will succeed, because the expected reference is newRef
and the expected stamp is newStamp.
AtomicStampedReference and the A-B-A Problem
The AtomicStampedReference is designed to solve the A-B-A problem. The A-B-A problem is when
a reference is changed from pointing to A, then to B and then back to A.
When using compare-and-swap operations to change a reference atomically, and making sure that only one thread can change the reference from an old reference to a new, detecting the A-B-A situation is impossible.
The A-B-A problem can occur in non-blocking algorithms. Non-blocking algorithms
often use a reference to an ongoing modification to the guarded data structure, to signal to other threads that
a modification is currently ongoing. If thread 1 sees that there is no ongoing modification (reference points to null),
another thread may submit a modification (reference is now non-null), complete the modification and
swap the reference back to null without thread 1 detecting it. Exactly how the A-B-A problem occurs
in non-blocking algorithms is explained in more detail in my tutorial about
non-blocking algorithms.
By using an AtomicStampedReference instead of an AtomicReference it is possible to
detect the A-B-A situation. Thread 1 can copy the reference and stamp out of the AtomicStampedReference
atomically using get(). If another thread changes the reference from A to B and then back to A, then
the stamp will have changed (provided threads update the stamp sensibly - e.g increment it).
The code below shows how to detect the A-B-A situation using the AtomicStampedReference:
int[] stampHolder = new int[1];
Object ref = atomicStampedReference.get(stampHolder);
if(ref == null){
//prepare optimistic modification
}
//if another thread changes the
//reference and stamp here,
//it can be detected
int[] stampHolder2 = new int[1];
Object ref2 = atomicStampedReference.get(stampHolder);
if(ref == ref2 && stampHolder[0] == stampHolder2[0]){
//no modification since
//optimistic modification was prepared
} else {
//retry from scratch.
}
| Tweet | |
Jakob Jenkov | |











