What is an atomic class

Characteristics of atomic classes

  • An integral
  • An operation is not interruptible, even in the case of multiple threads

Atomic classes act like locks to keep threads safe in the event of concurrency. Atomic classes, however, have certain advantages over locks:

  • Finer granularity: Atomic variables narrow the contention down to the variable level, which is as fine-grained as we can get, as locks are usually larger than atomic variables
  • More efficient: In general, using atomic classes is more efficient than using locks except in highly competitive cases

An overview of atomic classes

The Atomic base class demo

AtomicInteger common method

Public final int getAndSet(int newValue) public final int getAndSet(int newValue) -- star public Final int getAndIncrement() -- Star public Final int getAndDecrement() // Gets the current values and decreases public Final int getAndAdd(int delta) // Gets the current values and adds the expected values Boolean compareAndSet(int expect, int update) -- starCopy the code

Code demo

Public class CountExample {/** * public static int clientTotal = 5000; /** * public static int threadTotal = 200; Private static final AtomicInteger atomicCount = new AtomicInteger(0); private static Integer count = 0; Public static void main(String[] args) throws InterruptedException {Runnable Runnable = () -> {// Atomic class ++ operation atomicCount.incrementAndGet(); count++; }; ExecutorService executorService = Executors.newFixedThreadPool(threadTotal);for(int i = 0; i < clientTotal; i++) { executorService.execute(runnable); Thread.sleep(1000);} // Let the host Thread see the result of the accumulation. System.out.println("Result of multithreading summation of atomic classes." + atomicCount.get());
        System.out.println("Result of multithreading summation of ordinary variables."+ count); }}Copy the code

The output

Result of atomic multithreading summation 5000 result of common variable multithreading summation 4773

Atomic * Array demo

public class AtomicArrayDemo {

    public static void main(String[] args) {
        AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(1000);
        Incrementer incrementer = new Incrementer(atomicIntegerArray) ;
        Decrementer decrementer = new Decrementer(atomicIntegerArray) ;

        Thread[] threadsIncrementer = new Thread[100];
        Thread[] threadsDecrementer = new Thread[100];
        for (int i = 0; i < 100; i++) {
            threadsDecrementer[i] = new Thread(decrementer);
            threadsIncrementer[i] = new Thread(incrementer);
            threadsDecrementer[i].start();
            threadsIncrementer[i].start();

        }

        for(int i=0; i<100; i++){ try { threadsDecrementer[i].join() ; threadsIncrementer[i].join() ; } catch (InterruptedException e) { e.printStackTrace() ; }} // In normal case, the result should be 0, add 100 times, subtract 100 timesfor (int i = 0; i < atomicIntegerArray.length(); i++) {
            if(atomicIntegerArray.get(i) ! = 0) { System.err.println("Error found with array subscript" + i);
            }
        }

        System.out.println("End of run");

    }
}

class Decrementer implements Runnable{

    private AtomicIntegerArray array;

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

    @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 atomicIntegerArray) {
        this.array = atomicIntegerArray;
    }

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

An Atomic*Array means that every element in an Array is an Atomic base class

Atomic * Reference demonstration

AtomicReference : The AtomicReference class is not fundamentally different from AtomicInteger, which makes an integer atomic, and AtomicReference, which makes an object atomic, Of course, An AtomicReference is significantly more powerful than an AtomicInteger because an object can contain many attributes. The usage is similar to AtomicInteger.

Use AtomicReference to implement spin locking

public class SpinLock {

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

    public void lock() {
        Thread current = Thread.currentThread();
        while(! sign.compareAndSet(null,current)){ // System.out.println("Spin capture failed, capture again.");
        }
    }

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

    public static void main(String[] args) throws InterruptedException {
        SpinLock lock = new SpinLock();
        Runnable runnable = new Runnable(){

            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "Start trying to get a spin lock.");
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "Got a spin lock...");
                try {
                    Thread.sleep(1000);
                }catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                    System.err.println(Thread.currentThread().getName() + "Release lock"); }}}; Thread thread1 = new Thread(runnable); Thread thread2 = new Thread(runnable); thread1.start(); thread2.start(); thread1.join(); thread2.join(); }}Copy the code

Atomic * FieldUpdater demonstration

The main purpose of this type of atomic class is to upgrade ordinary variables to atomic variables. You might wonder why you didn’t just use the atomic class in the first place to avoid the atomic safety problem. Atomic*FieldUpdater has its own scenarios: where direct modifications to the original type are not allowed, but there are concurrency issues. But occasionally atomic get-set operations are required.

Operations of the atomic type consume far more resources than operations of the base type corresponding to the atomic type.

public class AtomicIntegerFieldUpdaterDemo 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{// update must be volatile int score; } public static void main(String[] args) throws InterruptedException { tom=new Candidate() ; peter=new Candidate( ) ; AtomicIntegerFieldUpdaterDemo r = new AtomicIntegerFieldUpdaterDemo() ; Thread t1 = new Thread(r) ; Thread t2 = new Thread(r) ; t1.start( ) ; t2.start( ) ; t1.join(); t2.join(); System.out.println("Common variable:"+ peter.score); tom.score++; // Use tom.score and scoreupater. Get (Tom)."Upgrade variable:" + tom.score + "-"+scoreUpdater.get(tom)); }}Copy the code

Use the attention points of Atomic*Updater

  • The visible range

The bottom layer of the updater is implemented through reflection, so the visibility range needs to be taken care not to set to invisible.

  • Does not support the static

Adder accumulator demo

LongAdder is more efficient than AtomicLong at high concurrency, but it’s essentially space for time. When the competition is fierce, LongAdder will correspond different threads to different cells for modification, which reduces the probability of conflict. It is the concept of multi-segment lock and improves the concurrency.

Efficiency comparison: AtomicLong VS LongAdder AtomicLong

public class AtomicLongDemo {

    public static void main(String[] args) throws InterruptedException {
        AtomicLong counter = new AtomicLong (0) ;
        ExecutorService service = Executors
                . newFixedThreadPool (20) ;
        Long start = System.currentTimeMillis();
        for(int i=0; i<10000; i++){ service.submit(new Task(counter)); } service.shutdown();while(! service.isTerminated()){ } Long end = System.currentTimeMillis(); System.out.println(counter.get()); System.out.println("AtomicLong time:" + (end - start));
    }

    private static class Task implements Runnable{

        private AtomicLong counter;

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

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

The results

LongAdder Duration: 2014

LongAdder

public class LongAdderDemo {

    public static void main(String[] args) {
        LongAdder counter = new LongAdder () ;
        ExecutorService service = Executors
                . newFixedThreadPool (20) ;
        Long start = System.currentTimeMillis();
        for(int i=0; i<10000; i++){ service.submit(new Task(counter)); } service.shutdown();while(! service.isTerminated()){ } Long end = System.currentTimeMillis(); System.out.println(counter.sum()); System.out.println("LongAdder time:" + (end - start));
    }

    private static class Task implements Runnable{

        private LongAdder counter;

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

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

The results

LongAdder Time: 224

Cause: AtomicLong needs to flush and refresh every time it adds, which consumes resources

Internally, the implementation principle of this LongAdder is different from that of AtomicLong just now. The implementation principle of AtomicLong just now is that every addition needs to be synchronized, so there will be more conflicts in the case of high concurrency, which will reduce efficiency.

In the case of LongAdder, each thread will have its own counter, which is only used in its own thread, so that it does not interfere with the counters of other threads.

LongAdder introduces the concept of piecewise accumulation, with a base variable and an array of cells [] participating in the count:

  • Base variable: the competition is not fierce and directly adds to the variable
  • Cell[] array: Contention is fierce, and each thread accumulates separately in its own Cell[I] slot

Sum () source code analysis

public long sum() { Cell[] as = cells; Cell a; long sum = base; // If sum is empty, base is addedif(as ! = null) {// If the sum of cells is not locked, the internal elements may be modified at the time of summingfor (int i = 0; i < as.length; ++i) {
                if ((a = as[i]) != null)
                    sum += a.value;
            }
        }
        return sum;
    }
Copy the code

Compare AtomicLong and LongAdder

  • At low contention, the AtomicLong and ongAdder classes have similar characteristics. But in a highly competitive situation, LongAdder’s expected throughput is much higher, but consumes more space
  • The scenarios that LongAdder is suitable for are those in which statistics and sums count, and LongAdder basically only provides add methods, whereas AtomicLong also has CAS methods

An Accumulator Accumulator

Accumulator is very similar to Adder. Accumulator is a more general-purpose version of Adder and is characterized by its flexibility

public class LongAccumulatorDemo { public static void main(String[] args) { LongAccumulator accumulator = new LongAccumulator((x, y) -> (x + y), 0); ExecutorService service = Executors . newFixedThreadPool (8) ; IntStream. Range (1, 10). The forEach (I - > service. Submit (() - > accumulator. The accumulate (I))); service.shutdown();while (!service.isTerminated()){

        }
        System.out.println(accumulator.getThenReset());
    }
}
Copy the code

Application scenario: This method is applicable to a large number of parallel computing scenarios.


  • Reference courses: MOOC – Play with Java concurrency tools, proficient in JUC, become a concurrency generalist