Concurrency is inevitable in Java development. In concurrent programming, we need to ensure that the program can get the correct results when accessed by multiple threads concurrently, that is, to achieve thread safety.
So what kind of standard can be called thread safety? Here is the definition of thread safety:
When multiple threads access a class, no matter what scheduling mode the runtime environment adopts or how these threads will execute alternately, and no additional synchronization or cooperation is required in the calling code, this class can show correct behavior, so this class is line safe.
Take a small example of thread insecurity. If we want to implement a function to count web page visits, we may first think of using count + + to count web page visits. Count + + can be divided into three independent operations:
- Gets the current value of the variable
- Give the current variable value obtained + 1
- Write back the new value to the variable
Assuming that the initial value of count is 10, when concurrent operations are performed, thread a and thread B may both perform 1 operations, and then perform 2 operations at the same time. A first goes to 3 operation + 1, and now the value is 11; Note that the current values obtained by AB just now are all 10, so after B performs the 3 operation, the value of count is still 11. This result obviously does not meet our requirements. Therefore, the count + + operation is not thread safe.
To achieve the goal of thread safety, we need to introduce the protagonist of this article - AtomicInteger. In this article, we introduce how thread safety is implemented inside AtomicInteger atomic types.
Not much to say, let's take a look at the source code of AtomicInteger #:
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 new Error(ex); } } private volatile int value; public AtomicInteger(int initialValue) { value = initialValue; } public AtomicInteger() { } public final int get() { 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)) return current; } } 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() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } } public final int getAndDecrement() { for (;;) { int current = get(); int next = current - 1; if (compareAndSet(current, next)) return current; } } public final int getAndAdd(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return current; } } public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } public final int decrementAndGet() { for (;;) { int current = get(); int next = current - 1; if (compareAndSet(current, next)) return next; } } public final int addAndGet(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return next; } } public String toString() { return Integer.toString(get()); } public int intValue() { return get(); } public long longValue() { return (long)get(); } public float floatValue() { return (float)get(); } public double doubleValue() { return (double)get(); } }
1, Properties 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 new Error(ex); } }
The first variable is unsafe. Unsafe is a tool class within JDK, which mainly implements platform related operations. The following is quoted from the official JDK document:
sun.misc.Unsafe It is a tool class used internally in JDK. It exposes some "unsafe" functions in the Java sense to the Java layer code, so that JDK can use more java code to realize some functions that are originally platform related and can only be realized by using native language (such as C or C + +). This class should not be used outside the JDK core class library.
The specific implementation of Unsafe is not related to the goal of this article. You only need to know that this code is to obtain the offset of value in heap memory.
The second variable is valueOffset, which is the memory offset. Offset is very important in AtomicInteger. Atomic operations of AtomicInteger are realized by memory offset.
2, Definition and volatile of Value
AtomicInteger itself is an integer, so the most important attribute is value. Let's see how it declares value
private volatile int value;
We see that value uses the volatile modifier, so what is volatile?
Volatile is equivalent to the weak implementation of synchronized, that is, volatile implements the semantics similar to synchronized, but there is no locking mechanism. It ensures that updates to the volatile field are communicated to other threads in a predictable manner.
volatile contains the following semantics:
- The Java storage model does not reorder the operations of volatile instructions: this ensures that the operations on volatile variables are performed in the order in which the instructions appear.
- Volatile variables are not cached in registers (visible only to owning threads) or other places not visible to the CPU. Each time, the results of volatile variables are always read from main memory. That is to say, for the modification of volatile variables, other threads are always visible, and do not use the variables inside their own thread stack. That is, in the happens before rule, after a write operation on a valatile variable, any subsequent read operation can be understood and the result of this write operation can be seen.
In short, volatile's function is that when one thread modifies a shared variable, another thread can read the modified value. When analyzing the source code of AtomicInteger, we know that this is enough.
3, Realizing secure self increment with CAS operation
There are many methods in AtomicInteger. For example, incrementAndGet() is equivalent to i + + and getAndAdd() is equivalent to i+=n. From the source code, we can see that the implementation of these methods is very similar, so we mainly analyze the source code of incrementAndGet() method.
The source code is as follows:
public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
The incrementAndGet() method implements the self increment operation. The core implementation is to obtain the current value and the target value (that is, value+1). If compareAndSet(current, next) returns success, the method returns the target value. So what does compareAndSet do? To understand this approach, we need to introduce CAS operations.
In the college operating system course, we learned the concepts of exclusive lock and optimistic lock. Exclusive lock means that after a thread obtains the lock, other threads need to hang until the thread holding the exclusive lock releases the lock; Optimistic lock is to assume that there is no conflict and operate directly. If there is a conflict and the operation fails, try again until the operation succeeds. CAS, Compare and Swap, is the mechanism used for optimistic locking.
The CAS operation in AtomicInteger is compareAndSet(). Its function is to fetch data from memory every time according to the memory offset (valueOffset), compare the fetched value with expect, and change the value in memory to update if the data is consistent.
In this way, the use of CAS ensures atomic operation. The principle of the other methods is the same as this, and there will be no more explanation here.
Before I looked at the source code of AtomicInteger, I thought it was an atomic operation implemented by synchronized #. After consulting the data, it is found that synchronized locks will affect performance, because synchronized locks in Java are exclusive locks. Although atomic operations can be realized, the concurrency performance of this implementation method is very poor.
4, Summary
To sum up, AtomicInteger mainly implements integer atomic operations to prevent abnormal results in case of concurrency. It mainly relies on the unsafe class in JDK to operate the data in memory. The volatile modifier ensures that value is in memory and that other threads can see that it is worth changing. CAS operation ensures that AtomicInteger can safely modify the value.