A comprehensive understanding of Java atomic variable classes

Posted by phrozenflame on Fri, 27 Dec 2019 12:04:02 +0100

📦 this article and sample source code have been filed in javacore

1, Introduction to atomic variable class

Why atomic variable classes are needed

To ensure thread safety is an important problem to be solved in Java Concurrent Programming. Java ensures the consistency of multithreaded data by three features: atomicity, visibility and orderliness.

  • The most common way to ensure thread safety is to use Lock mechanism (Lock, synchronized) to do mutually exclusive synchronization of shared data, so that at the same time, only one thread can execute a method or a code block, then the operation must be atomic and thread safe. The main problem of mutex synchronization is the performance problem caused by thread blocking and wake-up.
  • volatile is a lightweight lock (naturally better than ordinary locks), which guarantees the visibility of shared variables in multithreading, but cannot guarantee atomicity. Therefore, it can only be used in some specific scenarios.
  • In order to take into account the atomicity and lock performance problems, Java introduces CAS (mainly embodied in the Unsafe class) to realize non blocking synchronization (also known as Leeuwen lock). Based on CAS, a set of atomic tool classes is provided.

The function of atomic variable class

The granularity of atomic variable analogy lock is finer and lighter, and it is very important to realize high performance concurrent code on multiprocessor system. Atomic variables narrow the scope of competition to a single variable.

The atomic variable class is equivalent to a kind of generalized volatile variable, which can support atomic and conditional read / change / write operations.

Atomic classes internally use CAS instructions (hardware based support) to synchronize. These instructions are usually faster than locks.

Atomic variable classes can be divided into four groups:

  • Basic types
    • AtomicBoolean - boolean type atomic class
    • AtomicInteger - integer atomic class
    • AtomicLong - long integer atomic class
  • reference type
    • AtomicReference - reference type atomic class
    • AtomicMarkableReference - reference type atomic class with tag bits
    • AtomicStampedReference - reference type atomic class with version number
  • Array type
    • AtomicIntegerArray - reshape array atomic class
    • AtomicLongArray - long integer array atomic class
    • AtomicReferenceArray - reference type array atomic class
  • Property updater type
    • AtomicIntegerFieldUpdater - Atomic updater for integer fields.
    • AtomicLongFieldUpdater - Atomic updater for long integer fields.
    • AtomicReferenceFieldUpdater - atom updates fields in a reference type.

We will not discuss CAS, volatile and mutually exclusive synchronization in depth here. For more details, please refer to: Java Concurrent core mechanism

2, Basic types

Atomic classes of this type operate on Java basic types.

  • AtomicBoolean - boolean type atomic class
  • AtomicInteger - integer atomic class
  • AtomicLong - long integer atomic class

All of the above classes support CAS. In addition, AtomicInteger and AtomicLong also support arithmetic operations.

Tips:

Although Java only provides AtomicBoolean, AtomicInteger and AtomicLong, it can simulate other basic types of atomic variables. To simulate other primitive types of atomic variables, you can convert types such as short or byte to int, and use Float.floatToIntBits, Double.doubleToLongBits to convert floating-point numbers.

Because the implementation and usage of AtomicBoolean, AtomicInteger and AtomicLong are similar, this article only introduces AtomicInteger.

AtomicInteger usage

public final int get() // Get current value
public final int getAndSet(int newValue) // Gets the current value and sets the new value
public final int getAndIncrement()// Get the current value and auto increment
public final int getAndDecrement() // Get the current value and subtract from it
public final int getAndAdd(int delta) // Gets the current value, plus the expected value
boolean compareAndSet(int expect, int update) // If the input value (update) is equal to the expected value, set the value to the input value
public final void lazySet(int newValue) // Finally, it is set to newValue. Using the lazySet setting may cause other threads to read the old value for a short period of time.

Example of using AtomicInteger:

public class AtomicIntegerDemo {

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        AtomicInteger count = new AtomicInteger(0);
        for (int i = 0; i < 1000; i++) {
            executorService.submit((Runnable) () -> {
                System.out.println(Thread.currentThread().getName() + " count=" + count.get());
                count.incrementAndGet();
            });
        }

        executorService.shutdown();
        executorService.awaitTermination(30, TimeUnit.SECONDS);
        System.out.println("Final Count is : " + count.get());
    }
}

AtomicInteger implementation

Reading the source code of AtomicInteger, you can see the following definitions:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
	try {
		valueOffset = unsafe.objectFieldOffset
			(AtomicInteger.class.getDeclaredField("value"));
	} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

Explain:

  • The value - value attribute is decorated with volatile to make the modification of value visible to all threads in a concurrent environment.
  • Valueoffset - the offset of the value attribute, through which you can quickly locate the value field, which is the key to the implementation of AtomicInteger.
  • Unsafe - an attribute of type unsafe that provides CAS operations for AtomicInteger.

3, Reference type

Java data types are divided into basic data types and reference data types Java basic data type ).

The previous section mentioned atomic classes for basic data types. What if you want to do atomic operations for reference types? Java also provides related atomic classes:

  • AtomicReference - reference type atomic class
  • AtomicMarkableReference - reference type atomic class with tag bits
  • AtomicStampedReference - reference type atomic class with version number

The AtomicStampedReference class solves the ABA problem thoroughly in the reference type atomic class. Other CAS capabilities are similar to the other two classes, so it is the most representative. Therefore, this section only describes AtomicStampedReference.

Example: implement a simple spin lock based on AtomicReference

public class AtomicReferenceDemo2 {

    private static int ticket = 10;

    public static void main(String[] args) {
        threadSafeDemo();
    }

    private static void threadSafeDemo() {
        SpinLock lock = new SpinLock();
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executorService.execute(new MyThread(lock));
        }
        executorService.shutdown();
    }

    /**
     * Simple spin lock based on {@ link AtomicReference}
     */
    static class SpinLock {

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

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

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

    }

    /**
     * Using spin lock {@ link SpinLock} to process data concurrently
     */
    static class MyThread implements Runnable {

        private SpinLock lock;

        public MyThread(SpinLock lock) {
            this.lock = lock;
        }

        @Override
        public void run() {
            while (ticket > 0) {
                lock.lock();
                if (ticket > 0) {
                    System.out.println(Thread.currentThread().getName() + " Sold the first " + ticket + " Zhang ticket");
                    ticket--;
                }
                lock.unlock();
            }
        }

    }

}

The implementation of atomic class is based on CAS mechanism, but CAS has ABA problem (if you don't understand ABA problem, please refer to: CAS: the basic mechanism of Java concurrency ) It is to solve the ABA problem that we have AtomicMarkableReference and atomicstampededreference.

AtomicMarkableReference uses a Boolean value as the tag, and switches between true and false when modifying. This strategy can not solve the ABA problem fundamentally, but can reduce the probability of ABA occurrence. It is often used in scenarios such as caching or state description.

public class AtomicMarkableReferenceDemo {

    private final static String INIT_TEXT = "abc";

    public static void main(String[] args) throws InterruptedException {

        final AtomicMarkableReference<String> amr = new AtomicMarkableReference<>(INIT_TEXT, false);

        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(Math.abs((int) (Math.random() * 100)));
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    String name = Thread.currentThread().getName();
                    if (amr.compareAndSet(INIT_TEXT, name, amr.isMarked(), !amr.isMarked())) {
                        System.out.println(Thread.currentThread().getName() + " Object modified!");
                        System.out.println("The new objects are:" + amr.getReference());
                    }
                }
            });
        }

        executorService.shutdown();
        executorService.awaitTermination(3, TimeUnit.SECONDS);
    }

}

Atomicstampededreference uses an integer value as the version number. Before each update, compare the version number. If it is consistent, modify it. Through this strategy, the ABA problem can be fundamentally solved.

public class AtomicStampedReferenceDemo {

    private final static String INIT_REF = "pool-1-thread-3";

    private final static AtomicStampedReference<String> asr = new AtomicStampedReference<>(INIT_REF, 0);

    public static void main(String[] args) throws InterruptedException {

        System.out.println("The initial objects are:" + asr.getReference());

        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 3; i++) {
            executorService.execute(new MyThread());
        }

        executorService.shutdown();
        executorService.awaitTermination(3, TimeUnit.SECONDS);
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(Math.abs((int) (Math.random() * 100)));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            final int stamp = asr.getStamp();
            if (asr.compareAndSet(INIT_REF, Thread.currentThread().getName(), stamp, stamp + 1)) {
                System.out.println(Thread.currentThread().getName() + " Object modified!");
                System.out.println("The new objects are:" + asr.getReference());
            }
        }

    }

}

4, Array type

Java provides the following atomic classes for arrays:

  • AtomicIntegerArray - reshape array atomic class
  • AtomicLongArray - long integer array atomic class
  • AtomicReferenceArray - reference type array atomic class

There are already atomic classes for basic types and reference types. Why provide atomic classes for arrays?

The atomic class of array type provides the access semantics of volatile type for array elements, which is a feature that ordinary arrays do not have - arrays of volatile type only have the volatile semantics on array references.

Example: example of using AtomicIntegerArray (AtomicLongArray and AtomicReferenceArray are similar)

public class AtomicIntegerArrayDemo {

    private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);

    public static void main(final String[] arguments) throws InterruptedException {

        System.out.println("Init Values: ");
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            atomicIntegerArray.set(i, i);
            System.out.print(atomicIntegerArray.get(i) + " ");
        }
        System.out.println();

        Thread t1 = new Thread(new Increment());
        Thread t2 = new Thread(new Compare());
        t1.start();
        t2.start();

        t1.join();
        t2.join();

        System.out.println("Final Values: ");
        for (int i = 0; i < atomicIntegerArray.length(); i++) {
            System.out.print(atomicIntegerArray.get(i) + " ");
        }
        System.out.println();
    }

    static class Increment implements Runnable {

        @Override
        public void run() {

            for (int i = 0; i < atomicIntegerArray.length(); i++) {
                int value = atomicIntegerArray.incrementAndGet(i);
                System.out.println(Thread.currentThread().getName() + ", index = " + i + ", value = " + value);
            }
        }

    }

    static class Compare implements Runnable {

        @Override
        public void run() {
            for (int i = 0; i < atomicIntegerArray.length(); i++) {
                boolean swapped = atomicIntegerArray.compareAndSet(i, 2, 3);
                if (swapped) {
                    System.out.println(Thread.currentThread().getName() + " swapped, index = " + i + ", value = 3");
                }
            }
        }

    }

}

5, Property updater type

The updater class supports atomic operations based on the reflection mechanism to update field values.

  • AtomicIntegerFieldUpdater - Atomic updater for integer fields.
  • AtomicLongFieldUpdater - Atomic updater for long integer fields.
  • AtomicReferenceFieldUpdater - atom updates fields in a reference type.

There are certain restrictions on the use of these classes:

  • Because the object's attribute modification type atomic class is abstract, each use must use the static method newUpdater() to create an updater, and you need to set the class and attribute you want to update.
  • Field must be of type volatile;
  • It can't act on static variables;
  • Cannot act on a constant (final);
public class AtomicReferenceFieldUpdaterDemo {

    static User user = new User("begin");

    static AtomicReferenceFieldUpdater<User, String> updater =
        AtomicReferenceFieldUpdater.newUpdater(User.class, String.class, "name");

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 5; i++) {
            executorService.execute(new MyThread());
        }
        executorService.shutdown();
    }

    static class MyThread implements Runnable {

        @Override
        public void run() {
            if (updater.compareAndSet(user, "begin", "end")) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " Revised name = " + user.getName());
            } else {
                System.out.println(Thread.currentThread().getName() + " Modified by another thread");
            }
        }

    }

    static class User {

        volatile String name;

        public User(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }

        public User setName(String name) {
            this.name = name;
            return this;
        }

    }

}

Reference material

Topics: Programming Java Attribute