【Atomic】Atomic summary

This section will introduce the six atomic classes under the atomic package, focusing on AtomicInteger and LongAdder.

1 Atomic

Atomic classes can ensure thread safety in concurrent situations. There are two advantages for locks:

  • Finer granularity: narrowing the scope of competition to the variable level is the smallest granularity. The lock granularity is usually larger than the atomic class.
  • Higher efficiency: Generally, the efficiency is higher than the lock, and it is not as good as the lock in the case of high competition. [The reason is behind]

Insert picture description here

1.1 Basic types of atomic classes, take AtomicInteger as an example (emphasis)

Get current value

public final long get() 

The bottom layer of the atomic update operation is implemented based on CAS.

// 1 如果当前值等于预期值,则原子更新值
public final boolean compareAndSet(long expect, long update) {
    
    
    return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
}
// 底层
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);


// 2 获取并自增
public final long getAndIncrement() {
    
    
    return unsafe.getAndAddLong(this, valueOffset, 1L);
}

public final long getAndAddLong(Object var1, long var2, long var4) {
    
    
    long var6;
    do {
    
    
        // 获取当前值
        var6 = this.getLongVolatile(var1, var2);
        // 如果当前值是var6,则更新为var6+var4;否则自旋
    } while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));

    return var6;
}
  • Array type atomic class
    All elements in the array can be updated atomically.

  • Reference type atomic class

public final boolean compareAndSet(V expect, V update) {
    
    
    return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}

1.2 Ordinary variable upgrade atomic class, FieldUpater

A reflection-based utility that can atomically update the specified volatile int field of the specified class.

public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = 
AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");

Use scenario: occasionally an atomic get-set operation is needed, and most scenarios only require it to be a common type.
be careful:

  • Based on reflection, private variables cannot be upgraded.
  • Static variables modified by static cannot be upgraded and an exception will be thrown.

1.3 Adder accumulator, a typical LongAdder (used in high concurrency scenarios)

LongAdder implements the Number interface, but does not define the equals, hashCode, and compareTo methods. The instance is variable and the key storage is of no value.

Classes introduced in Java 8,The performance of LongAdder and AtomicLong is basically the same in low concurrency scenarios, and LongAdder is better in high concurrency scenarios. The essence is to exchange space for time.

In the fierce competition, LongAdder changes different threads to different Cells to reduce the probability of conflicts. It is the concept of multi-segment locks and improves concurrency.
The idea is similar to the idea of ​​ConcurrentHashMap segmentation (16 segments).

When AtomicLong is competing to update an atomic variable with high concurrency and multithreading, only one thread can succeed in CAS operation, causing a large number of threads to fail in competition, and spin to try CAS operations, which wastes CPU resources. When the thread CAS updates the atomic variable, it performs flush and refresh operations each time (the working memory is written to the main memory, and the main memory is written to the working memory).

Usage scenario: LongAdder is suitable for statistical summing and counting scenarios, basically only provides the add method, while AtomicLong also provides the cas operation.

Performance comparison with AtomicLong

Scenario: 20 threads do a total of 10,000 tasks, and each task does 10,000 increment operations.
LongAdder takes less than 200ms, while AtomicLong takes more than 2 seconds. The performance is more than 10 times worse.

Improvement and principle

The principle of AtomicLong is that every update operation is synchronized, so there are more conflicts and low efficiency when high concurrency.

LongAdder, each thread has its own counter. Each thread counter does not have a competition relationship, so during the addition process, there is no need for synchronization mechanism, flush and refresh, and there is no common counter to count all threads uniformly.

Using the concept of segmented summation, there are base variables and Cell arrays to participate in the counting: the competition is not fierce, and it is directly accumulated on the base variable; the competition is fierce, and each thread is scattered and accumulated in its own Cell array.

// sum方法源码
public long sum() {
    
    
    Cell[] as = cells; Cell a;
    long sum = base;
    if (as != null) {
    
    
        for (int i = 0; i < as.length; ++i) {
    
    
            if ((a = as[i]) != null)
                sum += a.value;
        }
    }
    return sum;
}

Note: The sum process is not locked, and the value obtained by adjusting the sum() method during the accumulation process may not be accurate.

Applicable scene

  1. As mentioned earlier, LongAdder performs better in high concurrency scenarios
  2. LongAdder only provides add and related methods, AtomicLong has more functions, such as CAS operation.

Accumulator accumulator, a typical LongAccumulator

More general, you can pass in expressions for calculation usage scenarios:

  • Massive calculations, parallel calculations
  • Does not require the order of calculation-this can be understood as 3 2 1 = 1 3 2, which has nothing to do with the order-satisfies the commutative law

The case code location of this article: https://gitee.com/dtyytop/advanced-java

Guess you like

Origin blog.csdn.net/LIZHONGPING00/article/details/114006961