An atomic operation may be a single step or multiple steps, but the sequence may not be disrupted, and it may not be cut so that only a part of it is performed (non-interruptibility). The core characteristic of atomicity is that the entire operation is treated as a whole and the resources remain consistent throughout that operation.
When it comes to atomicity, there are two aspects of Java to learn and master
- One is the already available Atomic packages in the JDK, both of which use CAS to perform Atomic operations on threads.
- Another is to use a locking mechanism to handle atomicity between threads. A Lock includes synchronized and Lock.
What is a CAS?
Compare and Swap (CAS) Compares and swaps. As a hardware synchronization primitive, the processor provides atomicity guarantees for basic memory operations. The CAS operation requires two values: an old value (the value before the expected operation) A and A new value B. During the operation, the old value is compared. If the old value does not change, the new value is exchanged. The sun.misc.unsafe class in Java provides several methods to implement CAS, including compareAndSwapint() and compareAndSwapLong().
The operating system usually changes the value of the variable by finding the memory address of the variable in memory and then changing the value of the variable, but as developers, we are writing code in the JVM, The Java API is not allowed to be like the operating system to find the variable by the memory address, and change the value of the variable. In general, we use the sun.misc.Unsafe class. Even though the object is in memory, it knows what the memory area of the object is, and the offset is what we call the offset. Here’s a simple implementation of the CAS operation via Unsafe
public class CounterUnsafe {
volatile int i = 0;
private static Unsafe unsafe = null;
/ / the offset
private static Long valueOffset;
static {
try {
// The Unsafe class is reflected
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
unsafe = (Unsafe) field.get(null);
// Get the offset of the I field
Field fieldi = CounterUnsafe.class.getDeclaredField("i");
valueOffset = unsafe.objectFieldOffset(fieldi);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch(IllegalAccessException e) { e.printStackTrace(); }}public void add(a) {
for(;;) {// Get the value of the variable from the offset
int current = unsafe.getIntVolatile(this,valueOffset);
// If the CAS operation succeeds, the spin is stopped
if(unsafe.compareAndSwapInt(this, valueOffset, current, current+1)) {
break; }}}}Copy the code
Atomic package
Since java1.5, provides the Java JDK. Util. Concurrent. The atomic package, this package of atoms in the action class (are completed using CAS mechanism atomic operation), provides a usage is simple, efficient performance and thread safe to update a variable way.
The atomic package contains 13 classes that are divided into four types: atomic update base, atomic update array, atomic update reference, and atomic update attribute. All of these classes are wrapper classes implemented using Unsafe.
Atomic update basic types
The Atomic package provides three classes for Atomic update base types: AtomicInteger, AtomicLong, and AtomicBoolean. Since the methods provided by all three classes are almost identical, AtomicInteger is used as an example.
AtomicInteger
public class CountExample {
// Total number of requests
public static int clientTotal = 5000;
// Number of concurrent threads
public static int threadTotal = 200;
// Variable declaration: count
public static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();// Create a thread pool
final Semaphore semaphore = new Semaphore(threadTotal);// Define the semaphore, which gives the number of concurrency allowed
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);// Define the counter latching
for (int i = 0; i<clientTotal; i++){ executorService.execute(()->{try {
semaphore.acquire();// Determine whether the process is allowed to execute
add();
semaphore.release();// Release the process
} catch (InterruptedException e) {
log.error("excption",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();// Ensure that the semaphore is reduced to 0
executorService.shutdown();// Close the thread pool
log.info("count:{}",count.get());// Value of variable
}
private static void add(a){
count.incrementAndGet();// Variable operations}}Copy the code
For the +1 operation on the count variable, incrementAndGet method is used. The source code for this method calls an unsafe.getAndaddint method:
public final int incrementAndGet(a) {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
Copy the code
The getAndAddInt method is implemented as follows:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
Copy the code
Count in the code can be understood as working memory in the JMM, where the underlying value is main memory.
GetAndAddInt calls the native method for Unsafe: GetIntVolatile and compareAndSwapInt get the current value in the do-while loop, and then check whether the current value is consistent with current through CAS. If the current value is consistent, if it means the value has not been modified by another thread, then set the current value to the current value +var4. If the unequal program enters the new CAS loop.
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
Copy the code
Looking through the code, we see that Unsafe only provides three CAS methods: CompareAndSwapObject, compareAndSwapInt, and compareAndSwapLong CompareAndSwapInt is then used for CAS, so atomic updates to char, float, and double variables can be implemented in a similar way.
AtomicLong versus LongAdder
LongAdder is a new class provided by Java8 that has the same effect as AtomicLong. First look at the code implementation:
/ / AtomicLong:
// Declare variables
public static AtomicLong count = new AtomicLong(0);
// Variable operations
count.incrementAndGet();
// Value of variable
count.get();
/ / LongAdder:
// Declare variables
public static LongAdder count = new LongAdder();
// Variable operations
count.increment();
// Value of variable
count
Copy the code
So why add a LongAdder when you have an AtomicLong? The reason is that the underlying CAS implementation keeps trying to change the target value in an infinite loop until the change succeeds. If the competition is not fierce, the success rate of modification is high, otherwise the failure rate is high. These repeated atomicity operations can cost performance in the event of failure.
For common types such as long and double, the JVM allows a 64-bit read or write operation to be split into two 32-bit operations.
For example, it can separate the internal core data of The AtomicLong into an array of values. When each thread accesses it, it can be mapped to one of the numbers and counted by algorithms such as hash. The final count result is the sum of the array. The hotspot data value is separated into multiple cells, and each cell maintains its own internal value. The actual value of the current object is accumulated from all the cells, thus effectively separating the hot spots and improving parallelism. This is equivalent to spreading the update pressure from a single point of AtomicLong across nodes. At low concurrency, the performance of AtomicLong can be ensured by directly updating the base. Performance is improved by dispersion at high concurrency.
public void increment(a) {
add(1L);
}
public void add(long x) {
Cell[] as; long b, v; int m; Cell a;
if((as = cells) ! =null| |! casBase(b = base, b + x)) {boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[getProbe() & m]) == null| |! (uncontended = a.cas(v = a.value, v + x))) longAccumulate(x,null, uncontended); }}Copy the code
If there are concurrent updates during the statistics, there may be errors in the statistics. In practice, LongAdder is preferred when dealing with high concurrency counts. Using AtomicLong is simpler and more efficient in cases where thread contention is less intense. Such as serial number generation (accuracy)
Two, atomic update array
AtomicIntegerArray: AtomicIntegerArray: AtomicIntegerArray: AtomicIntegerArray: AtomicIntegerArray; AtomicLongArray: Atomicupdate the elements of a long integer array; AtomicReferenceArray: AtomicReferenceArray: AtomicReferenceArray: AtomicReferenceArray: AtomicReferenceArray; Since the methods provided in each class are consistent, AtomicIntegerArray is an example.
AtomicIntegerArray
public class AtomicIntegerArrayTest {
private static int[] value = new int[] {1.2.3};
private static AtomicIntegerArray atomicInteger = new AtomicIntegerArray(value);
public static void main(String[] args){
atomicInteger.getAndSet(0.12);
System.out.println(atomicInteger.get(0));
System.out.println(value[0]); }}Copy the code
The value of the array is passed in by the constructor, and then AtomicIntegerArray makes a copy of the current array, so that when AtomicIntegerArray makes changes to the elements inside the array, it does not affect the array passed in.
Atomic update references
AtomicInteger of the atomic update base type can only update one variable. If you want to update multiple variables atomically, you need to use the class provided by the atomic update reference type. The atomic package provides the following classes: AtomicReference: Atomic update reference type; AtomicReferenceFieldUpdater: atomic updates the values in the reference type; AtomicMarkableReference: atomic updates with mark a reference type. Tag bits and reference types of a Boolean type can be updated atomically. The constructor is AtomicMarkableReference (V initialRef, booleaninitialMark). AtomicReference (AtomicReference) AtomicReference (AtomicReference)
AtomicReference
public class AtomicReferenceTest {
private static AtomicReference<User> reference = new AtomicReference<User>();
public static void main(String[] args){
User user = new User("tom".23);
reference.set(user);
User updateUser = new User("ketty".34);
reference.compareAndSet(user,updateUser);
System.out.println(reference.get().getName());
System.out.println(reference.get().getAge());
}
static class User{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName(a) {
return name;
}
public int getAge(a) {
returnage; }}}Copy the code
In this code, we first create a user object, then set the user object into the AtomicReference, and finally do the atomic update operation with compareAndSet. The result is as follows:
ketty
34
Copy the code
Atomic update properties
If you need to Atomic updates a field of an object, you need to use Atomic updates property related classes, in the Atomic provides several classes for Atomic updates properties: AtomicIntegerFieldUpdater: Atomic updates integer attribute update; AtomicLongFieldUpdater: AtomicLongFieldUpdater; AtomicStampedReference: AtomicStampedReference updates a reference type with a version number. This class associates an integer value with a reference that can be used for the atomic update data and the version number of the data, addressing the ABA problem that can occur when atomic updates are performed with CAS.
To get an atomic update field, you need two steps:
- Because the atomic update field classes are abstract, you must create an updater each time you use the static method newUpdater(), and you need to set the class and properties you want to update
- Updating a field (property) of a class must use the public volatile modifier
AtomicIntegerFieldUpdater consistent with AtomicLongFieldUpdater methods, for example with AtomicIntegerFieldUpdater below.
AtomicIntegerFieldUpdater
public class AtomicExample5 {
// Atomically update an instance of a class
private static AtomicIntegerFieldUpdater<AtomicExample5> updater
= AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class,"count");
@Getter
public volatile int count = 100;// The flag must be volatile and cannot be static
public static void main(String[] args) {
AtomicExample5 example5 = new AtomicExample5();
if(updater.compareAndSet(example5,100.120)){
log.info("update success 1,{}",example5.getCount());
}
if(updater.compareAndSet(example5,100.120)){
log.info("update success 2,{}",example5.getCount());
}else{
log.info("update failed,{}",example5.getCount()); }}} output the result of this method is: [the main] INFO. Com superboys. Concurrency. Example. Atomic. AtomicExample5 -update success1.120
[main] INFO com.superboys.concurrency.example.Atomic.AtomicExample5 - update failed,120As you can see, the value of count was changed only once.Copy the code
AtomicStampReference
What are ABA problems?
When CAS operates, other threads change the value of the variable A to B, but then change it to A. When this thread compares the value of the variable with the expected value A in the CAS method, it finds that the value of the variable has not changed, so CAS swaps the value of the variable. But in fact, the value of the variable has been changed by other variables, which is not consistent with the design idea.
Atomic updates a reference type with a version number. This class associates an integer value with a reference that can be used for the atomic update data and the version number of the data, addressing the ABA problem that can occur when atomic updates are performed with CAS.
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) {
return newPair<T>(reference, stamp); }}private volatile Pair<V> pair;
private boolean casPair(Pair<V> cmp, Pair<V> val) {
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
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 && // Exclude cases where the new reference and new version number are the same as the underlying value
newStamp == current.stamp) ||
casPair(current, Pair.of(newReference, newStamp)));
}
Copy the code
To explain the above source code: the class maintains a volatile Pair variable of type current. Pair is a private static class, and current can be interpreted as the underlying value. The parameters of the compareAndSet method are the expected reference, the new reference, the expected version number, and the new version number. The return logic determines whether the expected reference and version number match the underlying reference and version number, and excludes cases where the new reference and version number are the same as the underlying value (that is, no modification is required) (lines 3 and 4 of the return code section). The casPair method is executed and the CAS operation is invoked
The AtomicStampReference handles the idea that each time a variable is updated, the version number of the variable is +1. In the previous ABA problem, after two operations on the variable, the version number of the variable is changed from 1 to 3. This means that the version number of the variable is changed whenever the thread has operated on the variable. This solved the ABA problem.
synchronized
Java silently adds the lock and unlock methods before and after the critical section in synchronized locks. The nice thing is that the lock and unlock must always come in pairs, since forgetting to unlock means that other threads have to wait.
It depends on the JVM to implement the lock, so only one thread can operate on the keyword object at a time within its scope. Synchronized is a key word in Java. There are four main types of objects it can decorate:
Synchronized = synchronized = synchronized
The decorated code is called a synchronous block, and the scope is the part enclosed in braces. The object that functions is the object that calls this code.
public class SynchronizedExample {
public void test(int j){
synchronized (this) {for (int i = 0; i < 10; i++) {
log.info("test - {} - {}",j,i); }}}// Use the thread pool method to test:
public static void main(String[] args) {
SynchronizedExample example1 = new SynchronizedExample();
SynchronizedExample example2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(()-> example1.test(1));
executorService.execute(()-> example2.test(2)); }}Copy the code
Operations on different objects do not affect each other
Synchronized = synchronized = synchronized
A modified method is called a synchronous method, and its scope is the part enclosed in braces, and its scope is the object that called the code.
public class SynchronizedExample
public synchronized void test(int j){
for (int i = 0; i < 10; i++) {
log.info("test - {} - {}",j,i); }}// The validation method is the same as above. }Copy the code
Result: Operations on different objects do not affect each other
If the current class is a parent, a subclass that calls the parent class’s synchronized modified methods will not carry the synchronized property, since synchronized is not part of the method declaration
Synchronized = synchronized; synchronized = synchronized
The scope of the action is the part enclosed in synchronized braces, and the object of action is all objects of the class.
public class SynchronizedExample{
public static synchronized void test(int j){
for (int i = 0; i < 10; i++) {
log.info("test - {} - {}",j,i); }}// The validation method is the same as above. }Copy the code
Result: Only one thread can execute at a time
Synchronized = synchronized
public class SynchronizedExample{
public static void test(int j){
synchronized (SynchronizedExample.class){
for (int i = 0; i < 10; i++) {
log.info("test - {}-{}",j,i); }}}// The validation method is the same as above. }Copy the code
Result: Only one thread can execute at a time
Comparison of atomic manipulation methods
- Atomic: can maintain normal performance in competitive situations, better than Lock, can synchronize only one value at a time
- Synchronized: Synchronized
- Lock: Interruptible Lock, diversified synchronization, and maintain normal state when competition is fierce
Refer to the link
www.cnblogs.com/senlinyang/…
Blog.csdn.net/jesonjoke/a…
Blog.csdn.net/weixin_3926…
www.jianshu.com/p/7188fe52e…
Pay attention to the public number data craftsman, focus on Java big data field offline, real-time technology dry goods regular sharing! Personal Website: www.lllpan.top