Notes after reading the art of Java Concurrent Programming - 13 atomic operation classes in Java (Chapter 7)
1. Atomic update basic type class
The Atomic package provides the following three classes for updating basic types in an Atomic way:
- AtomicBoolean: atomic update boolean type
- AtomicInteger: atomic update integer
- AtomicLong: atomic update long
The above 3 classes as like as two peas, so here is only a case study of AtomicInteger.
The common methods of AtomicInteger are as follows:
- int addAndGet(int delta): atomically adds the entered value to the value in the instance (value in AtomicInteger) and returns the result.
- boolean compareAndSet(int expect,int update): if the entered value is equal to the expected value, the value will be set to the entered value atomically.
- int getAndIncrement(): atomically add 1 to the current value. Note that the value before self increment is returned here.
- void lazySet(int newValue): it will eventually be set to newValue. After setting the value with lazySet, other threads may still be able to read the old value in a short period of time.
- getAndSet(int newValue): the value set atomically to newValue and returns the old value
AtomicInteger sample code:
/** * @author xppll * @date 2022/1/14 17:57 */ public class AtomicIntegerTest { static AtomicInteger ai = new AtomicInteger(1); public static void main(String[] args) { System.out.println(ai.getAndIncrement());//1 System.out.println(ai.get());//2 } }
So how does getAndIncrement implement atomic operations?
The source code of getAndIncrement is as follows:
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
- The first step of the for loop body in the source code is to obtain the value stored in AtomicInteger
- The second step is to add 1 to the current value of AtomicInteger
- The third key step is to call the compareAndSet method to perform atomic update operation. This method first checks whether the current value is equal to current
- Equal to means that the value of AtomicInteger has not been modified by other threads, then update the current value of AtomicInteger to the value of next. If the compareAndSet method returns false, the program will enter the for loop and restart the compareAndSet operation.
The Atomic package provides three basic types of Atomic updates, but the basic types of Java include char, float and double. So the question is, how to update other basic types of atoms?
The source code of Unsafe is as follows:
/** * If the current value is expected, the atomic Java variable is updated to x * @return Returns true if the update is successful */ public final native boolean compareAndSwapObject(Object o, long offset, Object expected, Object x); public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x); public final native boolean compareAndSwapLong(Object o, long offset, long expected, long x);
Through the code, we found that Unsafe only provides three CAS methods: compareAndSwapObject, compareAndSwapInt and compareAndSwapLong.
Looking at the source code of AtomicBoolean, we find that it first converts Boolean to integer, and then uses compareAndSwapInt for CAS, so atomic updating char, float and double variables can also be implemented with similar ideas.
2. Atomic update array
Update an element in the array in an Atomic way. The Atomic package provides the following four classes:
- AtomicIntegerArray: atomically updates elements in an integer array.
- AtomicLongArray: atom updates the elements in a long array.
- AtomicReferenceArray: atomic updates the elements in the reference type array.
AtomicIntegerArray class mainly provides the atomic way to update the integer in the array. Its common methods are as follows.
- int addAndGet(int i,int delta): atomically adds the input value to the elements of index I in the array.
- boolean compareAndSet(int i,int expect,int update): if the current value is equal to the expected value, set the element at array position I to the update value atomically.
The methods provided by the above classes are almost the same, so this section only takes AtomicIntegerArray as an example.
Usage example code of AtomicIntegerArray:
/** * @author xppll * @date 2022/1/14 18:22 */ public class AtomicIntegerArrayTest { static int[] value = new int[]{1, 2}; static AtomicIntegerArray ai = new AtomicIntegerArray(value); public static void main(String[] args) { ai.getAndSet(0, 3); System.out.println(ai.get(0));//3 System.out.println(value[0]);//1 } }
It should be noted that the array value is passed in through the construction method, and then AtomicIntegerArray will copy the current array. Therefore, when AtomicIntegerArray modifies the internal array elements, the incoming array will not be affected
3. Atomic update reference type
AtomicInteger of Atomic update basic type can only update one variable. If you want to update multiple variables, you need to use this atom to update the class provided by the reference type. The Atomic package provides the following three classes:
- AtomicReference: atomic update reference type.
- Atomicreferencefield updater: updates fields in reference types.
- AtomicMarkableReference: atomic updates reference types with tag bits. You can update tag bits and reference types of a boolean type atomically. The construction method is AtomicMarkableReference (V initialRef, boolean initialMark).
The methods provided by the above classes are almost the same, so this section only takes AtomicReference as an example.
Usage example code of AtomicReference:
/** * @author xppll * @date 2022/1/14 17:23 */ public class AtomicReferenceTest { public static AtomicReference<User> atomicReference = new AtomicReference<User>(); public static void main(String[] args) { User user = new User("xpp", 20); atomicReference.set(user); User updateUser = new User("updateXpp", 24); atomicReference.compareAndSet(user, updateUser); System.out.println(atomicReference.get().getName());//updateXpp System.out.println(atomicReference.get().getOld());//24 } static class User { private String name; private int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }
First, a user object is built in the code, then the user object is set in AtomicReferenc, and finally compareAndSet method is used to update the atom, and the compareAndSet principle is implemented in AtomicInteger.
4. Atomic update field class
If you need to update a field in a class atomically, you need to update the field class atomically. The Atomic package provides the following three classes for Atomic field update:
- AtomicIntegerFieldUpdater: an updater for fields of atomic integer type.
- AtomicLongFieldUpdater: an updater that updates long fields.
- AtomicStampedReference: atomic updates reference types with version numbers. This class associates integer values with references, which can be used for atomic update data and data version number, and can solve the ABA problem that may occur when using CAS for atomic update.
To update field classes atomically, you need two steps.
- The first step is to use the static method newUpdater() to create an updater and set the classes and properties to be updated because the atomic update field classes are abstract classes.
- Second, the fields (properties) of the update class must use the public volatile modifier.
AstomicIntegerFieldUpdater code demonstration:
/** * @author xppll * @date 2022/1/14 17:46 */ public class AtomicIntegerFieldUpdaterTest { //Create an atomic updater and set the object class and object properties that need to be updated private static AtomicIntegerFieldUpdater<User> a = AtomicIntegerFieldUpdater.newUpdater(User.class, "old"); public static void main(String[] args) { //Age at which xpp started User xpp = new User("xpp", 10); //One year older, the output here is still the old age System.out.println(a.getAndIncrement(xpp));//10 //Output xpp current age System.out.println(a.get(xpp));//11 } public static class User { private String name; //Fields to be updated must use the public volatile modifier public volatile int old; public User(String name, int old) { this.name = name; this.old = old; } public String getName() { return name; } public int getOld() { return old; } } }