Java Concurrent Tool Learning - Atomic Tool Class

Preface

***

What are the categories?

The question of what an atomic class is is is not explained here, but it is guaranteed that operations on its variables are thread-safe under concurrent conditions. Atomic classes have certain advantages over locks. Its locks are finer granularity and more efficient than locks.

What kinds of atomic classes are there in Java? Give me a table directly.

categoryRepresentation Type
Basic types of atomsAtomicInteger
AtomicLong
AtomicBoolean
Atomic Classes of Array TypeAtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
Atomic Classes of Reference TypesAtomicReference
AtomicStampedReference
AtomicMarkableReference
Upgraded Atomic ClassesAtomicIntegerFieldUpdater
AtomicLongFieldUpdater
AtomicReferenceFieldUpdater
Adder accumulatorLongAdder,DoubleAdder
Accumulator accumulatorLongAccumulator
DoubleAccumulator

The basic type of atoms is the one that wraps up the eight basic types of atoms in our Java.

Atomic classes of array types, in which each element is a basic atomic type.

An atomic class of a reference type, that is, an atomic type referenced to an object

Atomic class of upgrade type, upgrade type for common field type

Adder accumulator, introduced in Java8, Accumulator is an accumulator that adds some functionality to Adder

Basic types

Only the basic uses of AtomicInteger are described in the Basic Types, which include the following 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();//Get the current value and increase by itself

public final int getAndDecrement();//Get the current value and subtract from it

boolean compareAndSet(int expect,int update);//If the value entered equals the expected value, set it atomically as the input value

Instance Code, Comparing Problems with Non-Atomic Classes

**
 * autor:liman
 * createtime:2021/11/15
 * comment:AtomicInteger A simple example
 * AtomicInteger
 * AtomicLong
 * AtomicBoolean
 * with AtomicInteger take as an example
 */
@Slf4j
public class AtomicIntegerDemo implements Runnable {

    private static final AtomicInteger atomicInteger = new AtomicInteger();

    private static volatile int basicCount = 0;

    public void incrementAtomicValue() {
        atomicInteger.getAndIncrement();
    }

    public void incrementBasicValue() {
        basicCount++;
    }

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

    public static void main(String[] args) throws InterruptedException {
        AtomicIntegerDemo atomicIntegerDemo = new AtomicIntegerDemo();
        Thread threaOne = new Thread(atomicIntegerDemo);
        Thread threadTwo = new Thread(atomicIntegerDemo);
        threaOne.start();
        threadTwo.start();
        threaOne.join();
        threadTwo.join();
        System.out.println("Results of Atomic Classes:"+atomicInteger.get());
        System.out.println("Results of Common Classes:"+basicCount);
    }
}

Run Results

Common classes have thread security issues, while atomic classes do not

Array type

Take AtomicIntegerArray for example, where multiple threads operate on the addition or subtraction of each element

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:AtomicIntegerArray Type of
 */
@Slf4j
public class AtomicArrayDemo {

    public static void main(String[] args) throws InterruptedException {
        //Array collection of 1000 AtomicInteger s
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
        Incrementer incrementer = new Incrementer(atomicIntegerArray);
        Decrementer decrementer = new Decrementer(atomicIntegerArray);
        //Create 100 new threads to add and 100 threads to subtract
        Thread[] incrementThread = new Thread[100];
        Thread[] decrementThread = new Thread[100];
        for(int i=0;i<100;i++){
            incrementThread[i] = new Thread(incrementer);
            decrementThread[i] = new Thread(decrementer);
        }
        //Start each thread in turn
        Arrays.stream(incrementThread).forEach(Thread::start);
        Arrays.stream(decrementThread).forEach(Thread::start);
        //Wait for all sub-threads to finish running
        for(int i=0;i<100;i++){
            incrementThread[i].join();
            decrementThread[i].join();
        }
        for(int i=0;i<atomicIntegerArray.length();i++){
            //An exception is determined if there is a non-zero in the array
            if(atomicIntegerArray.get(i)!=0){
                System.out.println("Discover Exceptions");
            }
        }
        System.out.println("End of run");
    }

}

//Operate addition on each element in an atomic array
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);//Minus 1 for the first element
        }
    }
}

//Operate subtraction on each element in an atomic array
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);//Minus 1 for the first element
        }
    }
}

Finally, you can see that in such a complex concurrency scenario, the data is still normal.

reference type

AtomicReference is a simple way to keep an object atomic, similar to AtomicInteger. We can use this class to simply implement a spin lock

**
 * autor:liman
 * createtime:2021/11/14
 * comment:Simple spin lock example
 */
public class SpinLockDemo {
	//Lock or maintain a reference to a thread, indicating the thread holding the lock
    private AtomicReference<Thread> sign = new AtomicReference<Thread>();

    //How threads acquire locks
    public void lock(){
        Thread currentThread = Thread.currentThread();
        //Acquire locks via cas. Getting a lock means setting the current sign to the current thread
        while(!sign.compareAndSet(null,currentThread)){
            System.out.println(currentThread.getName()+"Failed to acquire lock, spinning......");
        }
    }

    //The way the lock is released, the direct cas way is to leave the sign property empty to indicate that you are not held by any threads
    public void unlock(){
        Thread currentThread = Thread.currentThread();
        sign.compareAndSet(currentThread,null);
    }

    public static void main(String[] args) {
        SpinLockDemo spinLock = new SpinLockDemo();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                String currentThreadName = Thread.currentThread().getName();
                System.out.println(currentThreadName + "Start trying to acquire a spin lock");
                spinLock.lock();
                System.out.println(currentThreadName + "Successful spin lock acquisition");
                try {
                    //Simulate Business Processing
                    Thread.sleep(30);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    spinLock.unlock();
                    System.out.println(currentThreadName + "Successful release of spin lock");
                }
            }
        };
        Thread threadOne = new Thread(runnable);
        Thread threadTwo = new Thread(runnable);
        threadOne.start();
        threadTwo.start();
    }
}

Normal variable upgraded to atomic variable

Atomic classes in Java, in addition to the Atomic class variables and attributes already provided, have tools to upgrade ordinary variables directly to atomic variables, which requires the FieldUpdater in Atomic

Or use AtomicIntegerFieldUpdater as an example

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:Upgrade a common variable to an atomic variable
 */
public class AtomicIntegerFieldUpdateDemo implements Runnable{

    static Candidate tom;
    static Candidate peter;

    //When constructed, the first parameter is Class and the second is the property name within it
    public static AtomicIntegerFieldUpdater<Candidate> scoreUpdater
            =AtomicIntegerFieldUpdater.newUpdater(Candidate.class,"score");

    //For example, static internal class
    public static class Candidate{
        volatile int score;
    }

    @Override
    public void run() {
        for(int i=0;i<10000;i++){
            peter.score++;
            //Self-Increasing with Atomic Variables
            scoreUpdater.getAndIncrement(tom);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        tom = new Candidate();
        peter = new Candidate();
        AtomicIntegerFieldUpdateDemo runnable = new AtomicIntegerFieldUpdateDemo();
        Thread threadOne = new Thread(runnable);
        Thread threadTwo = new Thread(runnable);
        threadOne.start();
        threadTwo.start();
        threadOne.join();
        threadTwo.join();
        System.out.println("Common variable values:"+peter.score);
        System.out.println("Variable values upgraded to atomic classes:"+tom.score);
    }
}

The example is simple, but the AtomicIntegerFieldUpdater.newUpdater method, which supports attributes that can be accessed directly through reflection, is not supported if the attribute is declared private. Attributes declared as static are also not supported and cannot be upgraded to atomic variables.

Adder accumulator

This was introduced by Java8, a newer product. LongAdder is much more efficient than AtomicLong in high concurrency scenarios. This example illustrates the basic use of Adder

Example of using AtomicLong for cardinality

/**
 * autor:liman
 * createtime:2021/11/16
 */
@Slf4j
public class AtomicLongDemo {

    public static void main(String[] args) {
        AtomicLong atomicLongCounter = new AtomicLong(0);
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            executorService.submit(new Task(atomicLongCounter));
        }
        //Determine whether the thread pool is stopped
        executorService.shutdown();
        while(!executorService.isTerminated()){
            //When the thread pool does not perform the completed task, the main thread is idle until the end of the thread pool operation
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Time taken for tests"+(endTime-startTime));
        System.out.println(atomicLongCounter.get());

    }

    private static class Task implements Runnable{

        private AtomicLong atomicLongCounter;

        public Task(AtomicLong atomicLongCounter) {
            this.atomicLongCounter = atomicLongCounter;
        }

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

Run time: 1229 MS

Same functionality, done with LongAdder

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:LongAdder Accumulator instance
 * LongAdder Better than AtomicLong
 */
@Slf4j
public class LongAdderDemo {

    public static void main(String[] args) {
        LongAdder longAdderCounter = new LongAdder();
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++) {
            executorService.submit(new Task(longAdderCounter));
        }
        //Determine whether the thread pool is stopped
        executorService.shutdown();
        while(!executorService.isTerminated()){
            //The main thread is idle when the thread pool does not perform the completed task
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Time taken for tests"+(endTime-startTime));
        System.out.println(longAdderCounter.sum());

    }

    private static class Task implements Runnable{

        private LongAdder longAdderCounter;

        public Task(LongAdder longAdderCounter) {
            this.longAdderCounter = longAdderCounter;
        }

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

115 MS elapsed

In principle, AtomicLong synchronizes data into shared memory every time it modifies it

LongAdder does not. Each thread has its own counters, but it only counts the final results once, summarizes the local count values for each thread, and then synchronizes the final results to shared memory. Such optimization takes a short time.

Accumulator accumulator

This is similar to LongAdder, but more general and slightly more powerful.

Simple 1-100 sum, which runs in a multi-threaded fashion and is simpler than traditional traversal

/**
 * autor:liman
 * createtime:2021/11/16
 * comment:Demonstrate Accumulate accumulator
 */
@Slf4j
public class LongAccumulatorDemo {

    public static void main(String[] args) {
        LongAccumulator accumulator = new LongAccumulator((x,y)->x+y,0);
        ExecutorService executorService = Executors.newFixedThreadPool(8);
        IntStream.rangeClosed(1,100).forEach(t->executorService.submit(()->accumulator.accumulate(t)));
        executorService.shutdown();
        //Waiting for thread pool to end, main thread spins
        while(!executorService.isTerminated()){

        }
        System.out.println(accumulator.getThenReset());//This outputs the cumulative results of 1~100, which are obtained when running on multiple threads
    }
}

summary

Summary of the simple use of atomic classes. Next CAS

Tags: Java Back-end

Posted on Thu, 18 Nov 2021 12:36:11 -0500 by sansoo