Concurrency is an inevitable problem in Java development. When we do concurrent programming, we need to ensure that the program can be accessed concurrently by multiple threads to get the correct results, that is, to achieve thread safety. So what are the standards for thread safety? Here is a definition of thread safety:

A class is thread-safe when it is accessed by multiple threads, regardless of how the runtime environment is scheduled or how the threads will execute alternately, and does not require any additional synchronization or coordination in the calling code.

Take a small example of thread insecurity. If we want to implement a feature to count page views, the first thing we might think of is using count++ to count page views. Count++ can actually be broken down into three separate operations:

  1. Gets the current value of a variable
  2. Add +1 to the current value of the obtained variable
  3. Write back the new value to the variable

Assuming the initial value of count is 10, it is possible that thread A and thread B both perform operation 1 and then perform operation 2 simultaneously when performing concurrent operations. A goes to operation 3 +1, now it’s 11; Note that AB has obtained the current value of 10, so after B performs 3, count is still 11. This result is obviously not in line with our requirements. Therefore, this count++ operation is not thread-safe.

To achieve thread-safety goals, we need to introduce AtomicInteger, the protagonist of this article. This article describes how thread-safe is implemented inside the AtomicInteger atomic type.

Without further explanation, let’s have a look at AtomicInteger source code:

package java.util.concurrent.atomic;
import sun.misc.Unsafe;

public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    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 newError(ex); }}private volatile int value;

    public AtomicInteger(int initialValue) {
        value = initialValue;
    }

    public AtomicInteger(a) {}public final int get(a) {
        return value;
    }

    public final void set(int newValue) {
        value = newValue;
    }

    public final void lazySet(int newValue) {
        unsafe.putOrderedInt(this, valueOffset, newValue);
    }

    public final int getAndSet(int newValue) {
        for (;;) {
            int current = get();
            if (compareAndSet(current, newValue))
                returncurrent; }}public final boolean compareAndSet(int expect, int update) {
	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final boolean weakCompareAndSet(int expect, int update) {
	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public final int getAndIncrement(a) {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                returncurrent; }}public final int getAndDecrement(a) {
        for (;;) {
            int current = get();
            int next = current - 1;
            if (compareAndSet(current, next))
                returncurrent; }}public final int getAndAdd(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                returncurrent; }}public final int incrementAndGet(a) {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                returnnext; }}public final int decrementAndGet(a) {
        for (;;) {
            int current = get();
            int next = current - 1;
            if (compareAndSet(current, next))
                returnnext; }}public final int addAndGet(int delta) {
        for (;;) {
            int current = get();
            int next = current + delta;
            if (compareAndSet(current, next))
                returnnext; }}public String toString(a) {
        return Integer.toString(get());
    }


    public int intValue(a) {
	return get();
    }

    public long longValue(a) {
	return (long)get();
    }

    public float floatValue(a) {
	return (float)get();
    }

    public double doubleValue(a) {
	return (double)get(); }}Copy the code

Attributes defined in AtomicInteger

   // setup to use Unsafe.compareAndSwapInt for updates
    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 newError(ex); }}Copy the code

The first variable is Unsafe, an internal JDK utility class that implements platform-specific operations. The following is from the OFFICIAL JDK documentation:

Sun.misc.Unsafe is the utility class used internally in the JDK. It allows the JDK to make more use of Java code to implement platform-specific features that require native languages (such as C or C++) by exposing functionality that is “unsafe” in the Java sense to Java layer code. This class should not be used outside of the JDK core library.

The implementation of Unsafe is irrelevant for the purposes of this article, as long as you know that the code is for retrieving the offset of value in heap memory. The second variable is valueOffset, or memory offset. Offsets are important in AtomicInteger, where atomic operations depend on memory offsets.

Value definition and volatile

AtomicInteger itself is an integer, so the most important property is value, so how does it declare value

 private volatile int value;
Copy the code

We see that value uses the volatile modifier, so what is volatile?

Volatile is a weak implementation of synchronized, which means that volatile implements synchronized semantics without locking. It ensures that updates to volatile fields are notified to other threads in a predictable manner.

Volatile contains the following semantics:

  1. The Java storage model does not reorder operations on valatile instructions: this ensures that operations on volatile variables are performed in the order in which the instructions appear.
  2. Volatile variables are not cached in registers (visible only to owning threads) or elsewhere invisible to the CPU. The results of volatile variables are always read from main memory. This means that changes to volatile variables are always visible to other threads and do not use variables inside their own thread stack. That is, in the happens-before rule, after a write to a valatile variable, any subsequent reads will see the result of that write.

In short, the purpose of volatile is that when one thread changes a shared variable, another thread can read the changed value. This is all we learned when analyzing the AtomicInteger source code.

Third, CAS operation to achieve security increment

AtomicInteger has many methods, such as incrementAndGet() equals I ++ and getAndAdd() equals I +=n. We can see from the source code that the implementations of these methods are similar, so we’ll focus on the source code for the incrementAndGet() method.

The source code is as follows:

 public final int incrementAndGet(a) {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                returnnext; }}public final boolean compareAndSet(int expect, int update) {
	return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
Copy the code

The incrementAndGet() method implements the incrementing operation. The core implementation gets the current value and the target value (that is, value+1) and returns the target value if compareAndSet(current, Next) returns success. So what does compareAndSet do? To understand this method we need to introduce the CAS operation.

We learned about the concepts of exclusive locks and optimistic locks in college operating systems courses. An exclusive lock is a thread that acquires the lock and all other threads need to suspend until the thread that holds the exclusive lock releases the lock. Optimistic locking is to proceed directly assuming no conflicts and retry until the operation succeeds if it fails because of conflicts. The mechanism used for optimistic locking is CAS, Compare and Swap.

The CAS operation in AtomicInteger is compareAndSet(), which extracts data from memory at valueOffset each time, compares the retrieved value with Expect, and changes the in-memory value to update if the data is consistent.

Thus the use of CAS guarantees atomic operations. The other methods follow the same principle and will not be explained too much here.

Until I looked at AtomicInteger’s source code, I assumed that its internals were atomic operations implemented with synchronized. After consulting the data, it is found that synchronized will affect the performance, because synchronized lock in Java is an exclusive lock. Although atomic operation can be achieved, the concurrency performance of this implementation is very poor.

Four,

To sum up, AtomicInteger mainly implements atomic operations on integers to prevent abnormal results in concurrent situations. Its internal implementation relies on the UNSAFE class in the JDK to manipulate data in memory. The volatile modifier ensures that the value is visible to other threads in memory that it is worth changing. The CAS operation ensures that AtomicInteger can safely modify the value of value.