Atomic Classes
Introduction to Atomic Classes
Atomic translates to “atomic” in Chinese. Here, Atomic refers to an operation that is non-interruptible. Even when executed by multiple threads together, once an operation starts, it will not be interrupted by other threads.
So, simply speaking, an atomic class is a class that has atomic/atomic-operation characteristics.
The atomic classes in the concurrency package java.util.concurrent are located under java.util.concurrent.atomic.
Based on the type of data being operated on, the atomic classes in the JUC package can be divided into four categories
-
Primitive Types
Atomically updating primitive types
AtomicInteger: Atomic integer classAtomicLong: Atomic long classAtomicBoolean: Atomic boolean class
-
Array Types
Atomically updating a specific element inside an array
AtomicIntegerArray: Atomic array class for integersAtomicLongArray: Atomic array class for longsAtomicReferenceArray: Atomic array class for reference types
-
Reference Types
AtomicReference: Atomic reference typeAtomicMarkableReference: Atomic update of a reference type with a mark. This class associates a boolean mark with a reference.AtomicStampedReference: Atomic update of a reference type with a stamp. This class associates an integer value with a reference and can be used to address the ABA problem that might occur when performing atomic updates with CAS.
Note:
AtomicMarkableReferencecannot solve the ABA problem. -
Updater Types for Object Fields
AtomicIntegerFieldUpdater: Updater for atomically updating integer fieldsAtomicLongFieldUpdater: Updater for atomically updating long fieldsAtomicReferenceFieldUpdater: Updater for atomically updating fields inside reference types
Primitive Type Atomic Classes
Atomically updating primitive types
AtomicInteger: Atomic integer classAtomicLong: Atomic long classAtomicBoolean: Atomic boolean class
The three classes above provide nearly the same methods, so here we use AtomicInteger as an example to illustrate.
Common Methods of the AtomicInteger Class
public final int get() // get the current valuepublic final int getAndSet(int newValue)// get the current value, then set a new valuepublic final int getAndIncrement()// get the current value, then incrementpublic final int getAndDecrement() // get the current value, then decrementpublic final int getAndAdd(int delta) // get the current value, then add the deltaboolean compareAndSet(int expect, int update) // if the input equals the expected value, atomically set to updatepublic final void lazySet(int newValue)// eventually set to newValue; after lazySet, other threads may read the old value for a short periodAtomicInteger Common usage:
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicIntegerTest {
public static void main(String[] args) { int temvalue = 0; AtomicInteger i = new AtomicInteger(0); temvalue = i.getAndSet(3); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:0; i:3 temvalue = i.getAndIncrement(); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:3; i:4 temvalue = i.getAndAdd(5); System.out.println("temvalue:" + temvalue + "; i:" + i); //temvalue:4; i:9 }}Advantages of Primitive Type Atomic Classes
Let’s look at the advantages of primitive type atomic classes with a simple example.
-
In a multi-threaded environment, not using atomic classes cannot guarantee thread safety (primitive types)
class Test {private volatile int count = 0;// To execute count++ in a thread-safe way, you need to lockpublic synchronized void increment() {count++;}public int getCount() {return count;}} -
In a multi-threaded environment, using atomic classes guarantees thread safety (primitive types)
class Test2 {private AtomicInteger count = new AtomicInteger();public void increment() {count.incrementAndGet();}// After using AtomicInteger, you don't need to lock, and you can still achieve thread safety.public int getCount() {return count.get();}}
A Brief Analysis of AtomicInteger Thread-Safety Principles
AtomicInteger source code excerpt:
// setup to use Unsafe.compareAndSwapInt for updates(更新操作时提供“比较并替换”的作用) private static final Unsafe unsafe = Unsafe.getUnsafe(); private static final long valueOffset;
static { try { valueOffset = unsafe.objectFieldOffset (AtomicInteger.class.getDeclaredField("value")); } catch (Exception ex) { throw new Error(ex); } }
private volatile int value;AtomicInteger mainly relies on CAS (compare and swap), volatile, and native methods to guarantee atomic operations, thereby avoiding the high overhead of synchronized and significantly improving execution efficiency.
CAS works by comparing an expected value with the current value, and updating it to a new value only if they match. The Unsafe class’s objectFieldOffset() method is a native method used to obtain the memory address of the original value. In addition, value is a volatile variable and is visible in memory, so the JVM can ensure that any thread can obtain the latest value of this variable at any time.
Array-Type Atomic Classes
Atomically updating a single element in an array
AtomicIntegerArray: Atomic array class for integersAtomicLongArray: Atomic array class for longsAtomicReferenceArray: Atomic array class for reference types
The three classes above provide almost identical methods, so here we’ll use AtomicIntegerArray as an example.
AtomicIntegerArray Common Methods:
public final int get(int i) // get the value at index ipublic final int getAndSet(int i, int newValue)// return the current value at index i, and set it to newValuepublic final int getAndIncrement(int i)// get the value at index i, and increment that elementpublic final int getAndDecrement(int i) // get the value at index i, and decrement that elementpublic final int getAndAdd(int i, int delta) // get the value at index i, and add deltaboolean compareAndSet(int i, int expect, int update) // if the input value equals the expected, atomically set the index i element to updatepublic final void lazySet(int i, int newValue)// finally set the element at index i to newValue; may read the old value for a short time after thisAtomicIntegerArray Usage Example:
import java.util.concurrent.atomic.AtomicIntegerArray;
public class AtomicIntegerArrayTest {
public static void main(String[] args) { int temvalue = 0; int[] nums = { 1, 2, 3, 4, 5, 6 }; AtomicIntegerArray i = new AtomicIntegerArray(nums); for (int j = 0; j < nums.length; j++) { System.out.println(i.get(j)); } temvalue = i.getAndSet(0, 2); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndIncrement(0); System.out.println("temvalue:" + temvalue + "; i:" + i); temvalue = i.getAndAdd(0, 5); System.out.println("temvalue:" + temvalue + "; i:" + i); }
}Reference-Type Atomic Classes
Primitive type atomic classes can only update one variable. If you need to atomically update multiple variables, you should use reference-type atomic classes.
AtomicReference: Atomic reference typeAtomicStampedReference: Atomic update of a reference type with a stamp. This class associates an integer value with a reference and can be used to solve the ABA problem when performing atomic updates with CAS.AtomicMarkableReference: Atomic update of a reference type with a mark. This class associates a boolean mark with a reference
Usage Examples
AtomicReferenceClass:
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public static void main(String[] args) { AtomicReference < Person > ar = new AtomicReference < Person > (); Person person = new Person("SnailClimb", 22); ar.set(person); Person updatePerson = new Person("Daisy", 20); ar.compareAndSet(person, updatePerson);
System.out.println(ar.get().getName()); System.out.println(ar.get().getAge()); }}
class Person { private String name; private int age;
public Person(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; } public void setName(String name) { this.name = name; }
public int getAge() { return age; } public void setAge(int age) { this.age = age; }}The above code first creates a Person object, then puts it into an AtomicReference, and calls compareAndSet, which CAS-sets the ar if its value is person. If ar equals person, it is set to updatePerson. The implementation principle is the same as the compareAndSet method in AtomicInteger. The output after running the code is:Daisy20AtomicStampedReferenceClass Usage Example:
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceDemo { public static void main(String[] args) { // Instantiate, get current value and stamp final Integer initialRef = 0, initialStamp = 0; final AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(initialRef, initialStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// compare and set final Integer newReference = 666, newStamp = 999; final boolean casResult = asr.compareAndSet(initialRef, newReference, initialStamp, newStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", casResult=" + casResult);
// Get current value and current stamp int[] arr = new int[1]; final Integer currentValue = asr.get(arr); final int currentStamp = arr[0]; System.out.println("currentValue=" + currentValue + ", currentStamp=" + currentStamp);
// Individually set the stamp final boolean attemptStampResult = asr.attemptStamp(newReference, 88); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", attemptStampResult=" + attemptStampResult);
// Reset current value and stamp asr.set(initialRef, initialStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp());
// [Not recommended unless you fully understand the comments] // weak compare and set // Confusing! weakCompareAndSet ultimately calls compareAndSet. [Version: jdk-8u191] // but the comment says "May fail spuriously and does not provide ordering guarantees, // so is only rarely an appropriate alternative to compareAndSet." // todo Might be JVM forwarding in native method due to method name final boolean wCasResult = asr.weakCompareAndSet(initialRef, newReference, initialStamp, newStamp); System.out.println("currentValue=" + asr.getReference() + ", currentStamp=" + asr.getStamp() + ", wCasResult=" + wCasResult); }}The output is as follows:
currentValue=0, currentStamp=0currentValue=666, currentStamp=999, casResult=truecurrentValue=666, currentStamp=999currentValue=666, currentStamp=88, attemptStampResult=truecurrentValue=0, currentStamp=0currentValue=666, currentStamp=999, wCasResult=trueAtomicMarkableReferenceClass Usage Example:
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceDemo { public static void main(String[] args) { // Instantiate, get current value and mark final Boolean initialRef = null, initialMark = false; final AtomicMarkableReference<Boolean> amr = new AtomicMarkableReference<>(initialRef, initialMark); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// compare and set final Boolean newReference1 = true, newMark1 = true; final boolean casResult = amr.compareAndSet(initialRef, newReference1, initialMark, newMark1); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", casResult=" + casResult);
// Get current value and current mark boolean[] arr = new boolean[1]; final Boolean currentValue = amr.get(arr); final boolean currentMark = arr[0]; System.out.println("currentValue=" + currentValue + ", currentMark=" + currentMark);
// Individually set mark final boolean attemptMarkResult = amr.attemptMark(newReference1, false); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", attemptMarkResult=" + attemptMarkResult);
// Reset current value and mark amr.set(initialRef, initialMark); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked());
// [Not recommended unless you fully understand the comments] // weak compare and set // Confusing! weakCompareAndSet ultimately calls compareAndSet. [Version: jdk-8u191] // but the comment says "May fail spuriously and does not provide ordering guarantees, // so is only rarely an appropriate alternative to compareAndSet." // todo Might be JVM forwarding in native method due to method name final boolean wCasResult = amr.weakCompareAndSet(initialRef, newReference1, initialMark, newMark1); System.out.println("currentValue=" + amr.getReference() + ", currentMark=" + amr.isMarked() + ", wCasResult=" + wCasResult); }}The output is as follows:
currentValue=null, currentMark=falsecurrentValue=true, currentMark=true, casResult=truecurrentValue=true, currentMark=truecurrentValue=true, currentMark=false, attemptMarkResult=truecurrentValue=null, currentMark=falsecurrentValue=true, currentMark=true, wCasResult=trueAtomic Field Updaters for Object Fields
If you need to atomically update a field within a class, you should use the atomic field updater classes.
AtomicIntegerFieldUpdater: Updater for atomically updating integer fieldsAtomicLongFieldUpdater: Updater for atomically updating long fieldsAtomicReferenceFieldUpdater: Updater for atomically updating fields inside reference types
To atomically update an object’s field, two steps are required. First, because the atomic field updater classes are abstract, you must create an updater via the static method newUpdater(), specifying the class and the field to update. Second, the updated field must be declared as public volatile.
The three classes above provide almost identical methods, so here we’ll use AtomicIntegerFieldUpdater as an example.
Example usage of AtomicIntegerFieldUpdater:
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
public class AtomicIntegerFieldUpdaterTest { public static void main(String[] args) { AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "age");
User user = new User("Java", 22); System.out.println(a.getAndIncrement(user));// 22 System.out.println(a.get(user));// 23 }}
class User { private String name; public volatile int age;
public User(String name, int age) { super(); this.name = name; this.age = age; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
}If this article helped you, please share it with others!
Some information may be outdated





