Atomic Operation Classes in JUC and Their Principles

Keywords: Java JDK jvm Programming

Yesterday we looked briefly at the use of Unsafe. Today we look at how atoms in JUC use Unsafe and how they work!

 

1. Simple use of AtomicLong

Also remember that in the last blog we used the volatile keyword to modify a variable of type int, and then two threads did 10,000 + 1 operations on the variable, and the result wasn't 20,000. Now when we change to AtomicLong, you will see that the result is always 20,000!Interested ones can try it, code as follows

package com.example.demo.study;

import java.util.concurrent.atomic.AtomicLong;

public class Study0127 {

    //This is a global variable,Notice that an atomic class is used here AtomicLong
    public AtomicLong num = new AtomicLong();

    //Every time this method is called, it adds an operation to the global variable and executes 10,000 times
    public void sum() {
        for (int i = 0; i < 10000; i++) {
            //Using atomic classes incrementAndGet The method is to put num++Packaging into atomic operations
            num.incrementAndGet();
            System.out.println("current num The value of num= "+ num);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Study0127 demo = new Study0127();
        //Here are the two new threads,Called once each sum Method
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.sum();
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                demo.sum();
            }
        }).start();    
    }
}

 

2. Approaching the AtomicLong class

After JDK 1.5 in java, a package, JUC and package, full name is java.util.concurrent, we should have heard of a class ConcurrentHashMap, this map is interesting and interested to see the source code!There are many classes that need to be used in concurrency, such as AtomicInteger, AtomicLong, AtomicBoolean and so on. Actually, they are all similar. This time, let's take a brief look at AtomicLong, and several other classes are similar.

public class AtomicLong extends Number implements java.io.Serializable {
    
    //Obtain Unsafe Object, the last blog said why we can't use this method in our own class, but why can the official class be obtained like this?Because this class AtomicLong
    //Is rt.jar Under the package, this class uses Bootstrap Class loaded, so you can use this method
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    //value The offset of this field
    private static final long valueOffset;

    //judge jvm Supported or not long Type CAS operation
    static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();
    private static native boolean VMSupportsCS8();

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicLong.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
    //Here we use volatile Be sure to distinguish between atomicity and visibility for multithreaded visibility
    private volatile long value;

    //Two constructors don't say much
    public AtomicLong() {
    }
    public AtomicLong(long initialValue) {
        value = initialValue;
    }

 

Then let's look at AtomicLong's + 1 operation and see if it uses the unsafe class, just look at the getAndAddLong method

 

The method getAndAddLong contains CAS operations. It can be seen that if multiple threads call the incrementAndGet method to + 1 at the same time, only one thread will operate at the same time, while the others will continuously use CAS to try to + 1, and each time they try, they will go to main memory to get the latest value.

 public final long getAndAddLong(Object o, long offset, long delta) {
        long v;
        do {
    //This method is to retrieve the value of main memory, since volatile modifies that variable, the cache is useless v
= getLongVolatile(o, offset);     //Here's a dowhile infinite loop, where multiple threads constantly call the compareAndSwapLong method to set a value, which is actually CAS. Nothing special to say.
    //When a thread CAS successfully jumps out of the loop, otherwise it keeps trying and trying, which is the difference between CAS and thread blocking
} while (!compareAndSwapLong(o, offset, v, v + delta)); return v; }
//This CAS method is not visible, c implements the public final native boolean compareAndSwapLong(Object o, long offset,long expected,long x);

Interesting to see the other AtomicLong methods, many of which are the same, CAS is the core

 

3. The inadequacy of CAS and the recognition of LongAdder

From the example above, we can see that when using the AtomicLong class under multiple threads, only one thread can use that shared variable at the same time, and all other threads are in an infinite loop, which also consumes performance. If there are more threads, many threads are in their own infinite loop, or called multiple threads are spinningEach thread is spinning countless times really is a pit, consuming performance, we can find ways to spin a certain number of times, the thread will end up running, interested can understand the spin lock, in fact, this is such a principle, easy, haha!

After JDK8, a better class was provided to replace AtomicLong, LongAdder. It was said that only one thread was using that shared variable at the same time, and all other threads were spinning. If you could split the shared variable into multiple parts, could multiple threads operate at the same time?Then when the operation is completed, it can be combined again, with a little idea of dividing and treating, dividing and controlling, and finally combining.

How do we split that shared variable into parts?

In LongAdder, this is done by splitting that variable into a base (this is a long type with an initial value of 0) and a Cell (this encapsulates a long type with an initial value of 0). Each thread will only compete for many Cells, and adding up the values from multiple Cells and the base will be the end result; and one thread will not competeAfter competing for Cell, you won't be silly spinning, just find a way to compete for the next Cell.

The picture below shows

 

 

4. Simple use of LongAdder

In fact, the usage is similar to AtomicLong. If you are interested, you can try it. The result is always 20000.

package com.example.demo.study;

import java.util.concurrent.atomic.LongAdder;

public class Study0127 {

    //Use here LongAdder class
    public LongAdder num = new LongAdder();

    //Every time this method is called, it adds an operation to the global variable and executes 10,000 times
    public void sum() {
        for (int i = 0; i < 10000; i++) {
            //LongAdder A self-increasing operation of a class, equivalent to i++
            num.increment();
            System.out.println("current num The value of num= "+ num);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Study0127 demo = new Study0127();
        //Here are the two new threads,Called once each sum Method
        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.sum();
            }
        }).start();

        new Thread(new Runnable() {

            @Override
            public void run() {
                demo.sum();
            }
        }).start();    
    }
}

 

5. Walk into LongAdder

You can see from the above that a base can only be one, and there may be more than one Cell, and too many Cells are also memory-intensive, so Cells are not created at first and are created only when needed, also known as lazy loading.

We know that LongAdder inherits from Striped64

 

There are three fields in the Striped64 class, cell arrays for storing multiple cells, one for base not to mention more, and one for cellsBusy to implement spin locks. The status can only be 0 or 1 (0 means the Cell array is not initialized and expanded, nor is the Cell element being created, otherwise 1). This is used when creating cells, initializing the Cell array, or expanding the Cell arrayThis field guarantees that only one thread can perform one of these operations at a time.

 

1. Let's take a brief look at the Cell structure

It is clear from the code below that a Cell is a CAS operation on a long-type variable

@sun.misc.Contended //The purpose of this note is to avoid pseudo-sharing. As to what kind of pseudo-sharing, you will have a chance to talk about it later
static final class Cell {
    //each Cell Class is the variable that this declaration adds up later
    volatile long value;
    //Constructor
    Cell(long x) { value = x; }
    //Unsafe object
    private static final sun.misc.Unsafe UNSAFE;
    //value Offset of
    private static final long valueOffset;
    //In this static block of code is Get Unsafe Object and offset
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> ak = Cell.class;
            valueOffset = UNSAFE.objectFieldOffset
                (ak.getDeclaredField("value"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
     //CAS Operation, nothing to say
    final boolean cas(long cmp, long val) {
        return UNSAFE.compareAndSwapLong(this, valueOffset, cmp, val);
    }
}

 

 

2.LongAdder class self-increment method increment()

You can see that the increment() method actually calls the add method, and we need to be aware of what the add method does.

 

 

 

 

 

 public void add(long x) {
    Cell[] as; long b, v; int m; Cell a;
    //There cells Is Parent Striped64 Save to if not empty as , and then call casBase The method is CAS to base Update to base+x,That is, new every time x,
    //Because add(1L)The parameter passed in is 1, which means one at a time
    //If CAS If you succeed, you'll be done. If you CAS Fail, go inside
    if ((as = cells) != null || !casBase(b = base, b + x)) {
        boolean uncontended = true;
        //this if Judging the condition thieves, we divide these conditions into 1, 2, 3, 4 parts, the first three parts are used to decide which threads should access Cell Which one of the arrays Cell Element, last part for update Cell Value of
        //If the first, second and third parts are not satisfied, that is to say Cell Array exists and identified Cell Element, then go to the fourth part and update the corresponding Cell Value in Cell In class cas Method already seen)
        //If the first, second, and third parts satisfy one of them, that is to say Cell The array does not exist at all or the thread cannot find a corresponding Cell,On Execution longAccumulate Method
        if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x)))
            //Take a closer look at this method later. That's right Cell Array initialization and expansion, which is interesting
            longAccumulate(x, null, uncontended);
    }
}

//A simple CAS operation
final boolean casBase(long cmp, long val) {
    return UNSAFE.compareAndSwapLong(this, BASE, cmp, val);
}

  

For the above, it is interesting to see how to find the specified Cell. In the a = as [getProbe() & m] above, where M = the length of the array - 1, it is also a redundant operation here, and getProbe() is used to get the threadLocalRandomProbe of the current thread (current local thread detection value, initial value 0), which is actually a random number.The first call to this method is the first element of the array. If the first element of the array is null, then no corresponding Cell is found.

For a simple example of redundancy, I've forgotten a little too. For example, if random number 9 wants to redeem 4, we can use bitwise operations to redeem 9 &(4-1)=9&3=1001&0011=1;

Now let's focus on the longAccumulate method, which has a long code, so take a look at it separately

3.longAccumulate method

//This method is Cell Initialization and expansion of arrays, note that there is a parameter LongBinaryOperator,This is JDK8 New interface for functional programming, function signature is(T,T)->T,Here comes in null
final void longAccumulate(long x, LongBinaryOperator fn, boolean wasUncontended) {
    int h;
    //Initializes the current thread's threadLocalRandomProbd Value of, that is, to generate a random number
    if ((h = getProbe()) == 0) {
        ThreadLocalRandom.current(); // force initialization
        h = getProbe();
        wasUncontended = true;
    }
    boolean collide = false;                // True if last slot nonempty
    for (;;) {
        Cell[] as; Cell a; int n; long v;
        //This means initialization is complete
        if ((as = cells) != null && (n = as.length) > 0) {
            //This means random number and array size redundancy, and the result isThe current thread will match to Cell The index of the element, if the index corresponds to the Cell The elements in the array are null,Just add one Cell Throw objects in
            if ((a = as[(n - 1) & h]) == null) {
                //cellsBusy 0, representing the current Cell No expansion, initialization, or creation in progress Cell And so on, then the current thread can do this Cell Array is what you want
                if (cellsBusy == 0) {       // Try to attach new Cell
                    Cell r = new Cell(x);   // Optimistically create
                    //Look at the following Cell Array initialization, clearly stated, mainly settings cellsBusy 1, then match the current thread to Cell Set to newly created Cell object
                    if (cellsBusy == 0 && casCellsBusy()) {
                        boolean created = false;
                        try {               // Recheck under lock
                            Cell[] rs; int m, j;
                            if ((rs = cells) != null &&
                                (m = rs.length) > 0 &&
                                rs[j = (m - 1) & h] == null) {
                                rs[j] = r;
                                created = true;
                            }
                        } finally {
                            //take cellsBusy Reset to 0, indicating that other threads can Cell Array for whatever you want
                            cellsBusy = 0;
                        }
                        if (created)
                            break;
                        continue;           // Slot is now non-empty
                    }
                }
                collide = false;
            }


            else if (!wasUncontended)       // CAS already known to fail
                wasUncontended = true;      // Continue after rehash

            //Cell Execute when element exists CAS To update Cell Value in, here fn Is the formal parameter is null
            else if (a.cas(v = a.value, ((fn == null) ? v + x :
                                            fn.applyAsLong(v, x))))
                break;



            //When Cell The number of array elements is greater than CPU Number of
            else if (n >= NCPU || cells != as)
                collide = false;            // At max size or stale
            //Is there a conflict
            else if (!collide)
                collide = true;
            //Expansion Cell array,And the two above else if Look together
            //If current Cell Array element not reached CPU Number of new expansion whenever there is a conflict, the number of expansion is twice that of the original Cell[] rs = new Cell[n << 1];,Why and CPU How about counts?
            //Because when Cell Array elements and CPU Efficiency is highest when the number is the same, because each thread is one CPU Execute and then modify one of them Cell Value in
            //Use it here cellsBusy This field, initialized below Cell Same usage in arrays, let alone say
            else if (cellsBusy == 0 && casCellsBusy()) {
                try {
                    //Here is to create a new array twice as large as the original, then copy the elements of the original array to the new array, and change the original cells References to new arrays
                    if (cells == as) {      // Expand table unless stale
                        Cell[] rs = new Cell[n << 1];
                        for (int i = 0; i < n; ++i)
                            rs[i] = as[i];
                        cells = rs;
                    }
                } finally {
                    //Reset to 0 when used
                    cellsBusy = 0;
                }
                collide = false;
                continue;                   // Retry with expanded table
            }
            //The purpose here is to find all the threads after a long time Cell Number already sum CPU The same number, and then matched Cell Being used by other threads
            //ThereforeIn order to find a free Cell,So recalculate hash value
            h = advanceProbe(h);
        }



        //Initialization Cell array
        //Remember what it looks like cellsBusy This field can be either 0 or 1, when 0, explaining Cell Array is not initialized, expanded, or being created Cell Elements,
        //The opposite is 1, while casCellsBusy()The method is to use CAS take cellsBusy The value of 0 to 1 indicates that the current thread is initializing Cell Array, no other threads can expand
        //If a thread is initializing this Cell Array, when other threads expand, look at the above expansion, it will also execute casCellsBusy()Method proceed CAS The operation will fail because the expected value is 1, not 0
        else if (cellsBusy == 0 && cells == as && casCellsBusy()) {
            boolean init = false;
            try {                           // Initialize table
                if (cells == as) {
                    //Here we first create a new 2-capacity array, then use random numbers h&1,That is, the capacity balance of a random number over an array is indexed and each of the arrays is initialized Cell element
                    Cell[] rs = new Cell[2];
                    rs[h & 1] = new Cell(x);
                    cells = rs;
                    init = true;
                }
            } finally {
                //To reset this field to zero after initialization is complete means that other threads can then reset this field Cell Expanded capacity
                cellsBusy = 0;
            }
            if (init)
                break;
        }
     //Update base to base+x to indicate that base gradually adds up the values in each cell in the Cell array
else if (casBase(v = base, ((fn == null) ? v + x : fn.applyAsLong(v, x)))) break; // Fall back on using base } }

 

In fact, the longAccumulate method is to initialize the ell array when multithreaded, add cell elements and expand operations, and when one thread matches the Cell element, recalculates the random number when another thread finds it is in use, and then continues to match the other ell elements. Nothing particularly difficult!Not to mention that it's a long way to do these things

 

6. Summary

The core of this article is CAS. Let's briefly talk about the self-growth of AtomicLong, an atomic operation class. However, when there are many threads, there is a big disadvantage of using CAS. One thread is executing at the same time, all other threads are spinning. Spin consumes performance, so you can use a LongAdder class provided by JDK to replace it.Optimizes the values in AtomicLong to be a base and a cell array, where multiple threads compete. Assuming that the number of threads has the same number of CPUs, then each thread has a separate CPU to run, and then matches individually to an element in the cell array. If there is no match, the cell array will be initialized; if Ce is matchedIf the elements in the LL array are in use, it will take a long time to decide if a new Cell drop array can be created. If the array is full and the number of arrays is less than the number of CPUs, the expansion will take so long. If the expansion is over, or if the location in the matched Cell array is in use, then the conflict will be recalculated, using the remainder of a new random array and array.Get a new index and access the location of the corresponding Cell array.

It's still interesting to look at it carefully!

Posted by ckuipers on Wed, 29 Jan 2020 09:46:39 -0800