The word Atomic translates to atom. An atom is considered to be the smallest unit of operation, and a piece of code that is atomic means that it either succeeds or fails in its execution. Atomic operations are generally implemented at the bottom through CPU instructions. The classes in the Atomic package, on the other hand, allow us to achieve thread-safety in a multithreaded environment through a kind of lockless atomic operation.
Classes under the Atomic package are almost always wrapped by CAS operations using the Unsafe class. Unsafe is a class that is not part of the Java standard, or rather a backclass reserved for Java, which encapsulates most of the performance-enhancing concurrent and NIO operations in the JDK. Java is a compiled languages, unlike C language can support the operation of memory, under normal circumstances are to be carried out by the JVM create recycling operations such as memory, but this class provides some of the underlying operating direct memory related operation, we can also manually memory, but may see from the name of the class, the class is not safe, It’s not officially recommended.
The CAS theory
CAS contains three parameters CAS(V,E,N). V indicates the variable to be updated,E indicates the expected value, and N indicates the new value.
V is set to N only if V is equal to E. If V and E are different, another thread has done the update, and the current thread does nothing. Finally, CAS returns the true value of the current V. The CAS operation is carried out with optimism, always assuming that it can successfully complete the operation.
When multiple threads operate on a variable using CAS at the same time, only one will win and update successfully, while the rest will fail. Failing threads are not suspended, but are simply notified of their failure and allowed to try again, as well as aborting the operation. Based on this principle, CAS can detect interference from other threads to the current thread even without locking, and take appropriate action.
In the ATOMIC package of JDK8, there are about 16 classes, which can be divided into four categories according to the atomic update method: atomic update common type, atomic update array, atomic update reference, and atomic update field.
Atom updates common types
AtomicBoolean, AtomicInteger, and AtomicLong correspond to Boolean, integer, and long integers. As for the other basic types in Java, such as float, and so on, If necessary, you can refer to the source code of these classes to implement.
AtomicBoolean
The main interface
public final boolean get();
public final boolean compareAndSet(boolean expect, boolean update);
public boolean weakCompareAndSet(boolean expect, boolean update);
public final void set(boolean newValue);
public final void lazySet(boolean newValue);
public final boolean getAndSet(boolean newValue);Copy the code
All the operations here are normal, mainly CAS is used. There are few methods in this class, mostly described above, but the internal calculation first converts booleans to the number 0/1, and then performs subsequent calculations.
AtomicLong
The main interface
public final long get();
public final void set(long newValue);
public final void lazySet(long newValue);
public final long getAndSet(long newValue);
public final boolean compareAndSet(long expect, long update);
public final boolean weakCompareAndSet(long expect, long update);
public final long getAndIncrement();
public final long getAndDecrement();
public final long getAndAdd(long delta);
public final long incrementAndGet();
public final long decrementAndGet();
public final long addAndGet(long delta);
public final long getAndUpdate(LongUnaryOperator updateFunction);
public final long updateAndGet(LongUnaryOperator updateFunction);Copy the code
This is similar to AtomicInteger, which is described below.
AtomicInteger
The main interface
Public final int get(); // Sets the current value public final voidset(int newValue); Public final int getAndSet(int newValue); Public final Boolean compareAndSet(int expect, int u); Public final int getAndIncrement(); Public Final int getAndDecrement(); Public final int getAndAdd(int delta); Public final int incrementAndGet(); DecrementAndGet () public final int decrementAndGet(); Public final int addAndGet(int delta) public final int addAndGet(int delta);Copy the code
implementation
// Encapsulate an int to add or subtract private volatile int value; . Public final Boolean compareAndSet(int expect, int update) {// Cache-based CAS directive unblocking.returnunsafe.compareAndSwapInt(this, valueOffset, expect, update); }... public final intgetAndIncrement() {
for(;;) {// current value int current = get(); Int next = current + 1;if(compareAndSet(current, next)) {// If added successfully, return the current valuereturncurrent; } // If the add fails, another thread has modified the data and it does not match the expectation, // the loop continues indefinitely until it succeeds. Synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronized synchronizedCopy the code
Test this with a simple example:
AtomicInteger atomicInteger = new AtomicInteger(1);
System.out.println(atomicInteger.incrementAndGet()); // 2
System.out.println(atomicInteger.getAndIncrement()); // 2
System.out.println(atomicInteger.getAndAccumulate(2, (i, j) -> i + j)); // 3
System.out.println(atomicInteger.get()); // 5
System.out.println(atomicInteger.addAndGet(5)); Copy the code
Atomic update array
AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray, AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray An update is an operation that updates an element in an array.
Since the methods are the same as the update primitive type methods, here is a quick look at a few methods of the AtomicIntegerArray class. The other methods are similar.
AtomicIntegerArray
The main interface
Public final int get(int I); Public final int length(); Public final int getAndSet(int I, int newValue) public final int getAndSet(int I, int newValue); If the element with the i-th subscript equals expect, set it to update and return on successtruepublic final boolean compareAndSet(int i, int expect, int update); Public final int getAndIncrement(int I); Public final int getAndDecrement(int I); Public final int getAndAdd(int I, int delta) public final int getAndAdd(int I, int delta);Copy the code
implementation
// unsafe. private static final int base = unsafe.arrayBaseOffset(int[].class); Private final int[] array; Static {// The width of the object in the array, int, 4 bytes, scale = 4; int scale = unsafe.arrayIndexScale(int[].class);if((scale & (scale - 1)) ! = 0) throw new Error("data type scale not a power of two"); For 4, it is 00000000 00000000 00000000 00000100, its leading 0 is 29 // soshift = 2
shift= 31 - Integer.numberOfLeadingZeros(scale); } public final int get(int I) {returngetRaw(checkedByteOffset(i)); } private long checkedByteOffset(int I) {private long checkedByteOffset(int I) {if (i < 0 || i >= array.length)
throw new IndexOutOfBoundsException("index " + i);
returnbyteOffset(i); } // base: array base address, I <<shiftPrivate static long byteOffset(int I) {// I * 4 + basereturn ((long) i << shift) + base; Private int getRaw(long offset) {private int getRaw(long offset) {return unsafe.getIntVolatile(array, offset);
}Copy the code
Test this with a simple example:
AtomicIntegerArray array = new AtomicIntegerArray(5); array.set(0, 1); System.out.println(array.getAnddecrement (0)); // 1 System.out.println(array.addAndGet(0, 5)); / / 5Copy the code
Atomic update reference
Update the reference type atomic classes contains a AtomicReference (updated reference type), AtomicReferenceFieldUpdater (abstract class, update the fields in the reference type), AtomicMarkableReference classes that can update multiple variables at the same time.
AtomicReference
Similar to AtomicInteger, except that it encapsulates an object instead of an int, modifying the reference.
The main interface
public final V get();
public final void set(V newValue);
public final boolean compareAndSet(V expect, V update);
public final V getAndSet(V newValue);Copy the code
The test uses 10 threads, while trying to modify the String in the AtomicReference, and only one thread succeeds.
import java.util.concurrent.atomic.AtomicReference;
public class AtomicReferenceTest {
public final static AtomicReference<String> attxnicStr = new AtomicReference<String>("abc");
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread() {
public void run() {
try {
Thread.sleep(Math.abs((int) (Math.random() * 100)));
} catch (InterruptedException e) {
e.printStackTrace();
}
if (attxnicStr.compareAndSet("abc"."def")) {
System.out.println("Thread:" + Thread.currentThread().getId() + " change value to " + attxnicStr.get());
} else {
System.out.println("Thread:" + Thread.currentThread().getId() + " change failed!"); } } }.start(); }}}Copy the code
Atomic update field
If you update only one field in the object, you can update the field type provided by the atomic package: AtomicIntegerFieldUpdater AtomicLongFieldUpdater and AtomicStampedReference, the first two, as the name implies, is to update the int and long types, the last one is to update the reference type, This class provides a version number for resolving ABA problems that may occur during atomic updates through CAS. In front of the two classes and described above AtomicReferenceFieldUpdater some similar, is an abstract class, all need to instantiate through newUpdater method, and the requirement of field is the same.
AtomicStampedReference
ABA problem
Thread 1, the quasi-standby CAS, replaced the value of the variable from A to B. Before that, thread 2 replaced the value of the variable from A to C, and thread 3 replaced the value of C to A. Then when thread 1 executed the CAS, it found that the value of the variable was still A, so thread 1 CAS succeeded.
The main interface
// Set the following parameters: Expected Write New Value Expected Timestamp New timestamp Public Boolean compareAndSet(V expectedReference,V newReference,int expectedStamp,int newStamp) // Get the current object reference public V getReference() // Get the current timestamp public int getStamp() // Set the current object reference and timestamp public voidset(V newReference, int newStamp)Copy the code
Analysis of the
Stamp + 1 private static class Pair<T> {final T reference; final int stamp; private Pair(T reference, int stamp) { this.reference = reference; this.stamp = stamp; } static <T> Pair<T> of(T reference, int stamp) {returnnew Pair<T>(reference, stamp); } } private volatile Pair<V> pair; Public Boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair;return
expectedReference == current.reference &&
expectedStamp == current.stamp &&
((newReference == current.reference &&
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}Copy the code
test
Requirement: The background uses multiple threads to recharge the user, requiring only one recharge.
public class AtomicStampedReferenceDemo { static AtomicStampedReference<Integer> money=new AtomicStampedReference < Integer > (19, 0); Public staticvoid main(String[] args) {staticvoid main(String[] args) {staticvoid main(String[] args)for(int i = 0 ; i < 3 ; i++) {
final int timestamp=money.getStamp();
newThread() {
public void run() {
while(true) {while(true){
Integerm=money.getReference();
if(m<20){
if(money.com pareAndSet (m, m + 20, timestamp, timestamp + 1)) {System. Out. Println ("Balance less than 20 yuan, recharge successfully, balance :"+money.getReference()+"Yuan");
break; }}else{
//System.out.println("Balance over 20 yuan, no need to top up");
break; } } } } }.start(); } // user consumption thread, simulate consumption behavior newThread() {
publicvoid run() {
for(int i=0; i<100; i++){while(true){
int timestamp=money.getStamp();
Integer m=money.getReference();
if(m>10){
System.out.println("Greater than 10 yuan");if(money.com pareAndSet (m, m - 10, timestamp, timestamp + 1)) {System. Out. Println ("Successful consumption of 10 yuan, balance :"+money.getReference());
break; }}else{
System.out.println("There is not enough money.");
break;
}
}
try {Thread.sleep(100);} catch (InterruptedException e) {}
}
}
}.start();
}
}Copy the code
AtomicIntegerFieldUpdater
It allows ordinary variables to perform atomic operations.
The main interface
public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,
String fieldName);
public int incrementAndGet(T obj);Copy the code
- Updater can only modify variables that are visible to it. Because Updater uses reflection to get this variable. If the variable is not visible, an error occurs. For example, if score is declared private, this is not possible.
- To ensure that a variable is read correctly, it must be volatile. If we didn’t declare this type in our original code, we can simply declare it.
- Because CAS operations are assigned directly from offsets in the object instance, it does not support static fields (unsafe.objectFieldoffset () does not support static variables).
test
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; public class AtomicIntegerFieldUpdaterDemo { public static class Candidate { int id; / / if the int into atomicinteger, directly to the possible damage to the code is bigger / / so using AtomicIntegerFieldUpdater to encapsulate score volatile int score; } / / by reflecting public final static AtomicIntegerFieldUpdater < Candidate > scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score"); Public static AtomicInteger allScore = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { final Candidate stu = new Candidate(); Thread[] t = new Thread[10000];for (int i = 0; i < 10000; i++) {
t[i] = new Thread() {
public void run() {
if(Math. The random () > 0.4) {scoreUpdater. IncrementAndGet (stu); allScore.incrementAndGet(); }}}; t[i].start(); }for (int i = 0; i < 10000; i++) {
t[i].join();
}
System.out.println("score=" + stu.score);
System.out.println("allScore="+ allScore); }}Copy the code
Type introduced after JDK8
Prior to JDK8, we could basically use these classes for atomic operations with multiple threads, but the performance of these single CAS + spin operations would be a problem in high concurrency cases, so these classes are generally used for low concurrency operations. To address this problem, JDK8 introduces the following classes: DoubleAdder, LongAdder, DoubleAccumulator, LongAccumulator, these classes are improvements and enhancements to AtomicLong classes that inherit from Striped64.