Java multithreaded concurrency Unsafe and CAS understanding
1 What is Unsafe
Java cannot directly access the underlying operating system, but accesses it through a local (native) method. However, despite this, the JVM opens a backdoor, and there is an Unsafe-like in the JDK that provides hardware-level atomic operations.
Although the methods in this class are public, there is no way to use them, and the JDK API documentation does not provide any explanation for the methods of this class. In short, the use of Unsafe class is restricted. Only credited codes can obtain instances of this class. Of course, the classes in the JDK library can be used at will.
From the description on the first line, we can understand that Unsafe provides hardware-level operations, such as getting the location of a certain property in memory, such as modifying the field value of an object, even if it is private. However, Java itself is to block the underlying differences, and there are rarely such demands for general development.
public native long staticFieldOffset(Field paramField);
This method can be used to obtain the memory address offset of the given paramField, which is unique and fixed for the given field.
For example:
public native int arrayBaseOffset(Class paramClass); public native int arrayIndexScale(Class paramClass);
The former method is used to obtain the offset address of the first element of the array, and the latter method is used to obtain the conversion factor of the array, that is, the incremental address of the elements in the array.
Finally, look at three methods:
public native long allocateMemory(long paramLong); public native long reallocateMemory(long paramLong1, long paramLong2); public native void freeMemory(long paramLong);
Used to allocate memory, expand memory and free memory respectively.
Of course, this requires a certain C/C++ foundation and a certain understanding of memory allocation. This is also the reason why I have always believed that C/C++ developers will have advantages in changing careers to Java.
2 What is CAS
CAS, Compare and Swap is a technology commonly used in comparison and exchange, which is a common technology used when designing concurrency algorithms. The package is completely built on CAS. Without CAS, there will be no such package, which shows the importance of CAS.
Current processors basically support CAS, but the implementation of different manufacturers is different.CAS has three operands: memory value V, the old expected value A, the value B to be modified, and if and only if the expected value A and memory value V are the same, modify the memory value to B and return true, otherwise do nothing and return false.
CAS is also implemented through Unsafe. Let’s take a look at the three methods under Unsafe:
public final native boolean compareAndSwapObject(Object paramObject1, long paramLong, Object paramObject2, Object paramObject3); public final native boolean compareAndSwapInt(Object paramObject, long paramLong, int paramInt1, int paramInt2); public final native boolean compareAndSwapLong(Object paramObject, long paramLong1, long paramLong2, long paramLong3);
Take the comparison between the middle and exchange the Int value as an example. If we don't use CAS, the code will look like this:
public int i = 1; public boolean compareAndSwapInt(int j) { if (i == 1) { i = j; return true; } return false; }
Of course, there is definitely a problem with this code in concurrency. It is possible that thread 1 is running on line 5 and is about to run on line 7. Thread 2 is running. I am modifying i to 10 and thread switches back. Thread 1 has previously met if of line 5, so the two threads have modified the variable i at the same time.
The solution is also very simple. Just lock and synchronize the compareAndSwapInt method. In this way, the compareAndSwapInt method becomes an atomic operation. The same is true for CAS. Comparison and exchange are also a set of atomic operations, which will not be interrupted by the outside. First, obtain the current memory value V in the memory based on paramLong/paramLong1. Comparison of the memory value V with the original value A, if it is equal, modify it to the value B to be modified. Since CAS is both hardware-level operations, the efficiency will be higher.
3 Analyzing AtomicInteger Principle by CAS
The atomic operation classes under the package are all implemented based on CAS. Let’s analyze them with AtomicInteger. First, the definition of AtomicInteger class variables:
private static final Unsafe unsafe = (); private static final long valueOffset; static { try { valueOffset = (("value")); } catch (Exception ex) { throw new Error(ex); } } private volatile int value;
Regarding several member properties that appear in this code:
- 1. Unsafe is the core category of CAS, as mentioned earlier
- 2. valueOffset represents the offset address of the variable value in memory, because Unsafe obtains the original value of the data based on the memory offset address.
- 3. The value is modified with volatile, which is very critical.
Let’s find a method getAndIncrement to study how AtomicInteger is implemented, such as the addAndGet method we commonly use:
public final int addAndGet(int delta) { for (;;) { int current = get(); int next = current + delta; if (compareAndSet(current, next)) return next; } }
public final int get() { return value; }
How to achieve thread safety through CAS without locking this code, we might as well consider the execution of the method:
- 1. The original value of the value in AtomicInteger is 3, that is, the value of AtomicInteger in main memory is 3. According to the Java memory model, thread 1 and thread 2 each hold a copy of the value, with a value of 3.
- 2. Thread 1 runs to the third line and gets the current value of 3, thread switches
- 3. Thread 2 starts running, gets the value of 3, and uses CAS to compare the value of 3, which is more successful, and the memory is modified. At this time, the value in the memory changes, for example, 4, thread switching
- 4. Thread 1 resumes running, and using CAS comparison, I found that my value is 3 and the value in memory is 4, and I came to an important conclusion->At this time the value is being modified by another thread, so I can't modify it
- 5. The compareAndSet of thread 1 fails and loop judgment. Because the value is modified by volatile, it has the visibility feature. The change of the value of thread 2 can be seen by thread 1. As long as thread 1 finds that the value currently obtained is 4, the value in memory is also 4, which means that thread 2 has completed the modification of the value and thread 1 can try to modify it.
- 6. Finally, let me say, for example, at this time, thread 3 is also preparing to modify the value, it doesn't matter, because comparison-swap is an atomic operation that cannot be interrupted. Thread 3 modifies the value, and when thread 1 performs a comparisonAndSet, it will inevitably return false. In this way, thread 1 will continue to loop to obtain the latest value and perform compareAndSet until the obtained value is consistent with the value in memory.
During the entire process, the CAS mechanism is used to ensure thread safety for the modification of value.
4 Disadvantages of CAS
CAS looks beautiful, but this operation obviously cannot cover all scenarios under concurrency, and CAS is not semantically perfect. There is a logical vulnerability: if a variable V is first read and it is still A when preparing to assign, can we say that its value has not been modified by other threads?
If its value has been changed to B and then changed back to A during this period, the CAS operation will mistakenly believe that it has never been modified. This vulnerability is called the "ABA" problem for CAS operations.
To solve this problem, a tagged atomic reference class is provided
AtomicStampedReference
, it can ensure the correctness of CAS by controlling the version of variable values.
However, at present, this class is quite "unsparent". In most cases, the ABA problem will not affect the correctness of program concurrency. If the ABA problem needs to be solved, using traditional mutex synchronization may be more efficient to avoid atomic classes.
Summarize
The above is personal experience. I hope you can give you a reference and I hope you can support me more.