Multithreaded programming learning eight (atomic operations class).

brief introduction

Java provides the java.util.concurrent.atomic package in JDK 1.5, which contains atomic operations classes that provide a simple, efficient, thread-safe way to update a variable.There are four main types of atomic renewal methods, namely, basic types of atomic renewal, atomic renewal arrays, atomic renewal references, and atomic renewal properties.

The Atomic class basically uses Unsafe to keep threads safe.

public final class Unsafe {
    ...

    public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

    public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

    public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

    ...
}

In JDK 1.8, Doug Lea also added parallel accumulators such as LongAccumulator to the atomic package, providing a more efficient lock-free solution.

Atomic Update Basic Data Types

  • AtomicBoolean: Atomic Update Boolean Type
  • AtomicInteger: Atomic Update Integer
  • AtomicLong: Atomic Update Long Integer
public class AtomicIntegerTest {

    private static CountDownLatch countDownLatch = new CountDownLatch(1);

    private static AtomicInteger atomicInteger = new AtomicInteger(1);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                try {
                    countDownLatch.await();
                    // Atomically adds the current value to 1 and returns the previous value
                    System.out.print(atomicInteger.getAndIncrement() + " ");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
        // Threads compete simultaneously
        countDownLatch.countDown();
        Thread.sleep(2000);
        System.out.println();
        // Atomically adds the input value to the value in the example and returns the result.
        System.out.println(atomicInteger.addAndGet(10));
        // CAS Operation
        atomicInteger.compareAndSet(21, 30);
        System.out.println(atomicInteger.get());
    }
}

Atomic Update Array

  • AtomicIntegerArray: Atomic updates elements in an integer array
  • AtomicLongArray: Atoms update elements in long integer arrays
  • AtomicReferenceArray: Atomic update of elements in reference type array
public class AtomicReferenceArrayTest {

    // AtomicReferenceArray copies the current array (VALUE), so when AtomicReferenceArray modifies an internal array element, it does not affect the incoming array.
    private static Stu[] VALUE = new Stu[]{new Stu(System.currentTimeMillis(), "Zhang San"),new Stu(System.currentTimeMillis(), "Li Si")};

    private static AtomicReferenceArray<Stu> REFERENCE_ARRAY = new AtomicReferenceArray<>(VALUE);

    public static void main(String[] args) {
        // Modify the value of the specified location element
        REFERENCE_ARRAY.getAndSet(0, new Stu(System.currentTimeMillis(), "King Five"));
        System.out.println(REFERENCE_ARRAY.get(0));
        System.out.println(VALUE[0]);
    }
}

Atomic Update Reference

  • AtomicReference: Atomic update reference type
  • AtomicMarkableReference: Atomic update reference type with marker bit
  • AtomicStampedReference: Atomic update reference type with version number
public class AtomicStampedReferenceTest {

    private static Stu stu = new Stu(System.currentTimeMillis(), "Zhang San");
    /**
     * Updating objects with a version number prevents ABA problems in CAS.The principle is to compare not only the original value but also the version number.Similarly, you need to update the version number when updating
     */
    private static AtomicStampedReference<Stu> stampedReference = new AtomicStampedReference(stu, 1);

    public static void main(String[] args) {
        System.out.println(stampedReference.getReference());
        Stu newStu = new Stu(System.currentTimeMillis(), "Li Si");
        int stamp = stampedReference.getStamp();
        stampedReference.compareAndSet(stu, newStu, stamp, stamp++);
        System.out.println(stampedReference.getReference());
    }
}

Atomic Update Properties

  • AtomicIntegerFieldUpdater: Updater for atom update integer fields
  • AtomicLongFieldUpdater: Atomic Update Updater for Long Integer Fields
  • AtomicReferenceFieldUpdater: Field in Atomic Update Reference Type
public class AtomicReferenceFieldUpdaterTest {

    // Create an atomic updater and set the properties of the object classes and objects that need to be updated
    private static AtomicReferenceFieldUpdater<Stu, String> atomicUserFieldRef = AtomicReferenceFieldUpdater.newUpdater(Stu.class, String.class, "name");

    public static void main(String[] args) {
        Stu stu = new Stu(System.currentTimeMillis(), "Zhang San");
        atomicUserFieldRef.set(stu, "Li Si");
        System.out.println(stu.getName());
    }
}

It is important to note that updating the properties of a class must use the public volatile modifier.The following is the source code for AtomicReferenceFieldUpdater:

            if (vclass.isPrimitive())
                throw new IllegalArgumentException("Must be reference type");

            if (!Modifier.isVolatile(modifiers))
                throw new IllegalArgumentException("Must be volatile type");

1.8 Parallel accumulator

AtomicLong maintains a variable value that provides nonblocking atomic operations through CAS.Unfortunately, CAS failures require constant attempts through an infinite loop of spin locks, which wastes CPU resources in high concurrent N-multithreading scenarios.

So if you split a variable into multiple variables and let the same number of threads compete for multiple resources, then the performance problem will not be solved?Yes, this is the idea of LongAdder provided by JDK8.

The core idea of LongAdder is segmentation, which inherits from Striped64, which has two parameters, long base and Cell[] cells. Next, let's look at LongAddr's core code:

public void add(long x) {
        Cell[] as; long b, v; int m; Cell a;
        //When you want to add an element, first look to see if the cells array is empty, then try to see if it can be added directly to the base. If the thread competition is small, add to the base, and the function ends
        //If the cells are empty and there is a lot of competition and the cas fails, enter the if block and create the cells
        //If it's not empty, go into the cell array to see which one you can add to it
        if ((as = cells) != null || !casBase(b = base, b + x)) {
            boolean uncontended = true;
            //If cells are empty, add
            if (as == null || (m = as.length - 1) < 0 || (a = as[getProbe() & m]) == null || !(uncontended = a.cas(v = a.value, v + x)))
                longAccumulate(x, null, uncontended);
        }
    }

So to get the cumulative result, you can only call LongAdder's sum() method, which is the sum of the base + cell[] array elements.It is important to note that concurrent updates that occur at the time of the calculation may not be merged.

Tags: Java JDK

Posted on Sun, 08 Sep 2019 09:40:36 -0700 by assafbe