On atomic class

Keywords: Java Concurrent Programming

What are atomic classes and what are their functions?

  1. inseparable
  2. An operation is non interruptible and can be guaranteed even in the case of multithreading
  3. java.util.concurrent.atomic
  4. Atomic classes are similar to locks to ensure thread safety in concurrency. However, atomic classes have some advantages over locks
  5. Finer granularity: atomic variables can narrow the scope of competition to the variable level, which is the most fine-grained situation we can obtain. Generally, the granularity of locks is greater than that of atomic variables
  6. More efficient: generally, using atomic classes is more efficient than using locks, except in the case of high competition

Overview of 6 atomic classes

Atomic basic type atomic class
Atomic * basic type atomic classAtomicInteger,AtomicLong,AtomicBoolean
Atomic*Array array type atomic classAtomiclntegerArray,AtomicLongArray,AtomicReferenceArray
Atomic*Reference type atomic classAtomicReference,AtomicStampedReference,AtomicMarkableReference
Atomic*FieldUpdater upgrade type atomic classAtomiclntegerfieldupdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdate
Adder accumulatorLongAdder, DoubleAdder
Accumulator accumulatorLongAccumulator,DoubleAccumulator

Atomic basic type atomic class, taking AtomicInteger as an example

Common methods:

  • public final int get() / / get the current value
  • public final int getAndSet(int newValue) / / get the current value and set a new value
  • public final int getAndIncrement() / / take the current value and increment it automatically
  • public final int getAndDecrement() / / get the current value and subtract from it
  • public final int getAndAdd(int delta) / / take the current value and add the expected value
  • boolean compareAndSet(int expect, int update) / / if the entered value is equal to the expected value, set the value to the input value update atomically
public class AtomicIntegerDemo1 implements Runnable{
    
    private static final AtomicInteger atomicAtomicInteger = new AtomicInteger();
    private static volatile int basicCount = 0;

    public void incrementAtomic() {
        atomicAtomicInteger.getAndIncrement();
    }
    
    public void incrementBasic() {
        basicCount++;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            incrementAtomic();
            incrementBasic();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo1 atomicIntegerDemo1 = new AtomicIntegerDemo1();
        Thread t1 = new Thread(atomicIntegerDemo1);
        Thread t2 = new Thread(atomicIntegerDemo1);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Results of atomic classes: " + atomicAtomicInteger.get());//20000
        System.out.println("Results of ordinary variables: " + basicCount);//Less than 20000
    }
}

Atomic*Array array type atomic class

package com.zeny.threadstudy.atomics;

import java.util.concurrent.atomic.AtomicIntegerArray;

@SuppressWarnings("all")
public class AtomicArrayDemo {

    public static void main(String[] args) {
        int len = 100;
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(len);
        Thread[] threadsIncrementer = new Thread[len];
        Thread[] threadsDecrementer = new Thread[len];
        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);
        for (int i = 0; i < len; i++) {
            threadsIncrementer[i] = new Thread(incrementer);
            threadsDecrementer[i] = new Thread(decrementer);
        }
        for (int i = 0; i < len; i++) {
            threadsIncrementer[i].start();
            threadsDecrementer[i].start();
        }
        while (Thread.activeCount() > 2);
        for (int i = 0; i < len; i++) {
            if (atomicIntegerArray.get(i) != 0) {
                System.out.println("An error was found " + i);
            }
        }
        System.out.println("End of operation");
    }
}
class Decrementer implements Runnable {

    private AtomicIntegerArray array;

    public Decrementer(AtomicIntegerArray array) {
        this.array = array;
    }

    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndDecrement(i);
        }
    }
}
class Incrementer implements Runnable {

    private AtomicIntegerArray array;

    public Incrementer(AtomicIntegerArray array) {
        this.array = array;
    }

    @Override
    public void run() {
        for (int i = 0; i < array.length(); i++) {
            array.getAndIncrement(i);
        }
    }
}

Atomic* Reference type atomic class

AtomicReference: the function of AtomicReference class is not fundamentally different from AtomicInteger. AtomicInteger can ensure atomicity of an integer, while AtomicReference can ensure atomicity of an object. Of course, AtomicReference is obviously more powerful than AtomicInteger, because an object can contain many attributes, and its usage is similar to AtomicInteger.

public class SpinLock {

    private AtomicReference<Thread> sign = new AtomicReference<>();

    public void lock() {
        Thread current = Thread.currentThread();
        while (!sign.compareAndSet(null, current)) {
        }
    }
    
    public void unlock() {
        Thread current = Thread.currentThread();
        sign.compareAndSet(current, null);
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName() + "Start trying to acquire spin lock");
            spinLock.lock();
            System.out.println(Thread.currentThread().getName() + "Spin lock acquired");
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println(Thread.currentThread().getName() + "Release spin lock");
                spinLock.unlock();
            }
        };
        Thread t1 = new Thread(runnable);
        Thread t2 = new Thread(runnable);
        t1.start();
        t2.start();
    }
}

Upgrade ordinary variables to atomic classes: upgrade the original variables with AtomicIntegerFieldUpdater

◆ AtomicIntegerFieldUpdater upgrades ordinary variables

◆ usage scenario: an atomic get set operation is occasionally required

public class AtomicIntegerFiledUpdaterDemo implements Runnable{

    static Candidate tom;
    static Candidate peter;

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

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            peter.score++;
            scoreUpdater.getAndIncrement(tom);
        }
    }

    public static class Candidate {

        volatile int score;

    }

    public static void main(String[] args) throws InterruptedException {
        tom = new Candidate();
        peter = new Candidate();
        AtomicIntegerFiledUpdaterDemo demo = new AtomicIntegerFiledUpdaterDemo();
        Thread t1 = new Thread(demo);
        Thread t2 = new Thread(demo);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("Common variable: " + peter.score);
        System.out.println("Upgrade variable: " + tom.score);
    }
}

AtomicIntegerFieldUpdater considerations

◆ visible range

◆ static is not supported

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.IllegalArgumentException
	at java.base/jdk.internal.misc.Unsafe.objectFieldOffset0(Native Method)
	at java.base/jdk.internal.misc.Unsafe.objectFieldOffset(Unsafe.java:955)
	at java.base/java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:434)
	at java.base/java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:94)
	at com.zeny.threadstudy.atomics.AtomicIntegerFiledUpdaterDemo.<clinit>(AtomicIntegerFiledUpdaterDemo.java:12)

◆ volatile statement

Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.IllegalArgumentException: Must be volatile type
	at java.base/java.util.concurrent.atomic.AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl.<init>(AtomicIntegerFieldUpdater.java:420)
	at java.base/java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater(AtomicIntegerFieldUpdater.java:94)
	at com.zeny.threadstudy.atomics.AtomicIntegerFiledUpdaterDemo.<clinit>(AtomicIntegerFiledUpdaterDemo.java:12)

Adder accumulator

◆ it is a relatively new class introduced by Java 8

◆ long adder is more efficient than AtomicLong, but the essence is space for time

◆ when the competition is fierce, LongAdder modifies different threads on different cell s to reduce the probability of conflict. It is the concept of multi-stage lock and improves the concurrency

◆ code demonstration

public class AtomicLongDemo {


    public static void main(String[] args) {
        test();
    }
    public static void test() {
        new Thread(() -> testAtomicLong()).start();
        new Thread(() -> testLongAddr()).start();
    }

    public static void testAtomicLong() {
        ExecutorService service = Executors.newFixedThreadPool(20);
        AtomicLong counter = new AtomicLong(0);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new TaskAtomicLong(counter));
        }
        service.shutdown();
        while (!service.isTerminated());
        long endTime = System.currentTimeMillis();
        System.out.println("AtomicLong result: " + counter.get() + ", time: " + (endTime - startTime) + " ms");
    }

    public static void testLongAddr() {
        ExecutorService service = Executors.newFixedThreadPool(20);
        LongAdder counter = new LongAdder();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            service.submit(new TaskLongAddr(counter));
        }
        service.shutdown();
        while (!service.isTerminated());
        long endTime = System.currentTimeMillis();
        System.out.println("LongAddr result: " + counter.sum() + ", time: " + (endTime - startTime) + " ms");
    }

    private static class TaskAtomicLong implements Runnable {

        private AtomicLong counter;

        public TaskAtomicLong(AtomicLong counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 1000000; i++) {
                counter.incrementAndGet();
            }
        }
    }
    private static class TaskLongAddr implements Runnable {

        private LongAdder counter;

        public TaskLongAddr(LongAdder counter) {
            this.counter = counter;
        }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++) {
                counter.increment();
            }
        }
    }

}

◆ here is a demonstration of the performance of AtomicLong in the case of multithreading. There are 20 threads accumulating the same AtomicLong

◆ due to the fierce competition, AtomicLong needs to flush and refresh every addition, resulting in a great consumption of resources.

Compare the principles of LongAdder and AtomicLong:

  1. Internally, the implementation principle of this LongAdder is different from AtomicLong. The implementation principle of AtomicLong is that each addition needs to be synchronized, so there will be more conflicts at high concurrency, which will reduce the efficiency
  2. At this time, each thread will have its own counter, which is only used to count in its own thread. In this way, it will not interfere with the counters of other threads. There is no competition between them. Therefore, in the process of adding, there is no need for synchronization mechanism, flush and Refresh, There is also no common counter to count all threads uniformly

Why is LongAddr fast

  1. LongAdder introduces the concept of piecewise accumulation. There is a base variable and a Cell [] array inside to count
  2. base variable: the competition is not fierce, and it is directly accumulated on this variable
  3. Cell [] array: the competition is fierce, and each thread is scattered and accumulated into its own slot cell []
public long sum() {
    Cell[] cs = cells;
    long sum = base;
    if (cs != null) {
        for (Cell c : cs)
            if (c != null)
                sum += c.value;
    }
    return sum;
}

Compare the usage scenarios of AtomicLong and LongAdder:

  1. Under low contention, AtomicLong and LongAdder have similar characteristics. However, in the case of fierce competition, the expected throughput of LongAdder is much higher, but it consumes more space
  2. LongAdder is suitable for statistical summation and counting, and LongAdder basically only provides the add method, while AtomicLong also has the Cas method

Accumulator accumulator

Accumulator is very similar to Adder. Accumulator is a more general version of Adder

It is suitable for mass computing and parallel computing without sequential logic

Calculate the sum of 1 + 2 + 3 +... + 8 + 9

LongAccumulator accumulator = new LongAccumulator(Long::sum, 0);
ExecutorService service = Executors.newFixedThreadPool(8);
IntStream.range(1, 10).forEach( i -> service.submit(() -> accumulator.accumulate(i)));
service.shutdown();
while (!service.isTerminated());
System.out.println(accumulator.getThenReset());

Calculate 9!

public static void main(String[] args) {
    LongAccumulator accumulator = new LongAccumulator((x ,y) -> x * y, 1);
    ExecutorService service = Executors.newFixedThreadPool(8);
    IntStream.range(1, 10).forEach( i -> service.submit(() -> accumulator.accumulate(i)));
    service.shutdown();
    while (!service.isTerminated());
    System.out.println(accumulator.getThenReset());
}

Posted by web_master on Fri, 05 Nov 2021 20:59:19 -0700