CAS is a modern operating system, to solve the problem of concurrency is an important means, recently in the Eureka to see the source code. Many CAS operations were encountered. Today we will review the CAs in Java in a systematic way.
Read this article and you will learn:
What is CAS
What the CAS implementation principle is.
The application of CAS in reality
Spin lock
Atomic type
Current limiting device
The disadvantages of CAs what are CAS
CAS: Full name compare and swap, literal: compare and swap, a CAS involves the following operations:
We assume that the original data in memory V, the old expected value A, needs to be modified by the new value B.
Compare A and V for equality. Comparison
If the comparison is equal, write B to V. Exchange
Returns whether the operation was successful.
When multiple threads perform a CAS operation on a resource at the same time, only one thread succeeds, but it does not block other threads, and other threads only receive a signal that the operation failed. Visible CAS is actually an optimistic lock. how CAS is implemented
Follow the Atominteger code we go down, we can find the final call is Sum.misc.Unsafe this class. Look at the name Unsafe is an unsafe class, which utilizes Java classes and packages in the visibility of the rules of a right place in the hole. Unsafe This class makes a certain compromise on the Java security standards for speed.
And looking down, we found that the compareandswapint of unsafe is Native:
Public Final native Boolean compareandswapint (Object var1, long var2, int var4, int var5);
In other words, these CAS methods should be using a local method. So the specific implementation of these methods requires us to go to the JDK's source code search.
So I downloaded a OPENJDK source code to continue to explore, we found that in the/jdk9u/hotspot/src/share/vm/unsafe.cpp there are such codes:
{CC "Compareandsetint", CC "(" OBJ "J" "I" "I" ") Z", Fn_ptr (Unsafe_compareandsetint)},
This involves, JNI calls, interested students can learn by themselves. After searching for unsafe_compareandsetint, we found:
Unsafe_entry (Jboolean, Unsafe_compareandsetint (jnienv *env, Jobject UNSAFE, jobject obj, jlong offset, jint E, Jint x)) { OOP p = jnihandles::resolve (obj); jint* addr = (Jint *) Index_oop_from_field_offset_long (P, offset); Return (Jint) (Atomic::cmpxchg (x, addr, e)) = = e; } unsafe_end
Eventually we finally saw the core code ATOMIC::CMPXCHG.
Continue to the bottom of the search, in the file java/jdk9u/hotspot/src/os_cpu/linux_x86/vm/atomic_linux_x86.hpp have such code:
inline jint atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value, cmpxchg_memory_order order)  {   INT MP  = OS::IS_MP (); __asm__ volatile (LOCK_IF_MP (% 4) "cmpxchgl %1, ( %3) " : "=a" (exchange_value) : "R" (Exchange_value) , "A" (compare_value), "R" (dest), "R" (MP) : cc ", " Memory "); return exchange_value; }
We know by filename that, for different operating systems, JVMs should have different implementations for ATOMIC::CMPXCHG. Since our service is basically using 64-bit Linux, we will look at the implementation of Linux_x86.
Let's keep looking at the code:
__asm__ means this is a piece of inline assembly code. That is, use assembler code in C language.
The volatile here is a bit like JAVA, but not for memory visibility, but to tell the compiler that the code that accesses the variable is no longer optimized.
The meaning of Lock_if_mp (% 4) is simpler, if the operating system is multi-threaded, add a LOCK.
Cmpxchgl is the "comparison and exchange" of the assembly edition. But we know to compare and exchange, there are three steps, not atoms. So a LOCK is added to the multi-core case, and the CPU hardware guarantees his atomicity.
Let's see how LOCK is implemented. We went to Intel's official web site to see that lock in the early implementation of the Cup is directly blocking the bus, so that the realization of the visibility is very low efficiency. Later optimized for X86 CPU has the ability to lock a specific memory address, when this particular memory address is locked, it can prevent other system bus to read or modify this memory address.
On the bottom exploration of CAS we're done here. Let's summarize how the CAS is implemented in JAVA:
The CAs used by the Java unsafe is the CAS operation provided by this class.
Unsafe CAs is dependent on the JVM's implementation of ATOMIC::CMPXCHG for different operating systems
The implementation of ATOMIC::CMPXCHG uses a compiled CAS operation and uses the lock signal provided by CPU hardware to guarantee its atomic CAS application
Understand the principles of CAs let's go on to see the application of CAs: Spin locks
public class Spinlock {private atomicreference<thread> sign =new atomicreference<> (); public void Lock () {Thread current = Thread.CurrentThread (); while (!sign. Compareandset (null, current) {}} public void Unlock () {Thread current = Thread.CurrentThread () ; Sign. Compareandset (current, null); } }
The so-called spin lock, I think that the name of a considerable image, in the lock (), always while () loop until the CAS operation is successful. Atomicinteger's Incrementandget ()
Public final int Getandaddint (Object var1, long var2, int var4) {int var5; do {VAR5 = This.getintvolatile (var1, var2); while (!this.compareandswapint (var1, Var2, VAR5, VAR5 + VAR4)); return VAR5; }
This is the same thing as a spin lock, which is always while until the operation succeeds. token bucket Current limiter
The so-called token bucket limiter, is the system at a constant speed to increase the number of tokens in the bucket. Obtain a token from the token bucket before each request. Access is available only if a token is obtained. Refuse to provide service when there is no token in the token barrel. Let's look at how the Eureka Limiter uses CAS to maintain token and distribution in a multithreaded environment.
public class ratelimiter { private final long Ratetomsconversion; private final atomicinteger consumedtokens = new atomicinteger (); private final atomiclong Lastrefilltime = new atomiclong (0); @Deprecated public ratelimiter () { this ( Timeunit.seconds); } public ratelimiter ( Timeunit averagerateunit) { switch ( Averagerateunit) { case SECONDS: ratetomsconversion = 1000; break; case MINUTES: ratetomsconversion = 60 * 1000; break; default: throw new illegalargumentexception ("timeunit of " + averageRateUnit + ) is not supported "); } &NBSP;&NBSP;}&NBSP;&NBSP;&NBSP;&NBSP;&NBSP;//provides a way for the outside world to get token public boolean acquirE (int burstsize, long averagerate) { Return acquire (Burstsize, averagerate, system.currenttimemillis ()); } public boolean acquire (int burstsize, long Averagerate, long currenttimemillis) { if (burstsize <= 0 | | averagerate <= 0) { // instead of throwing exception, we just let all the traffic go return true; } //Add token refilltoken (Burstsize, averagerate, currenttimemillis); //consumption token return Consumetoken (burstsize); } private void Refilltoken (Int burstsize, long averagerate, long currenttimemillis) { long refilltime = lastrefilltime.get (); long timeDelta = currentTimeMillis - refilltime; //according to frequency calculation needs to increase how much token long newtokens = timedelta * averagerate / rateToMsConversion; if (newtokens > 0) { long newrefilltime = refilltime == 0 ? currentTimeMillis : refillTime + newtokens * ratetomsconversion / averagerate; // CAS guarantee that there is only one thread entering the fill if (Lastrefilltime.compareandset (refilltime, newrefilltime)) { while (True) { int currentlevel = consumedtokens.get (); int adjustedlevel = math.min (currentlevel, burstsize); // in case burstsize decreased int newLevel = (int) math.max (0, adjustedlevel - newtokens); // while true until the update is successful if (Consumedtokens.compareandset ( Currentlevel, newlevel)) { return; } } } } } private boolean consumetoken (Int burstSize) { while (True) { int currentlevel = consumedtokens.get (); if (currentlevel >= burstsize) { return false; } // while true until no token or Get to so far if ( Consumedtokens.compareandset (currentlevel, currentlevel + 1)) { return true; } } } public void reset () { consumedtokens.set (0); lastrefilltime.set (0); } }
So comb the role of CAS in token bucket limiters. is to ensure that in a multi-threaded situation, not blocking the thread's fill token and consumption token. Induction
Through the above three applications, we summarize the application scenarios of CAS:
The use of CAS can prevent thread blocking.
In most cases, we use a while true until we succeed. CAS Disadvantage
The problem with ABA is that a value changed from A to B and a, and the CAS operation did not reveal that the value had changed, and that the processing could be done using a version that carried a similar timestamp atomicstampedreference
Performance problems, most of the time we use the while true to modify the data until it succeeds. The advantage is that it is extremely fast, but when the number of threads keeps increasing, performance drops significantly, because each thread needs to execute, CPU time. Summary
CAS is one of the important ideas of the whole programming. There is a CAS figure in the implementation of the entire computer. The microscopic view of the compiled CAS is the cornerstone of the operating system-level atomic operation. From a programming language perspective, CAS is the cornerstone of multithreaded non-blocking operations. In the macroscopic view, in the distributed system, we can use the idea of CAS to utilize the external storage of similar redis, also can implement a distributed lock.
From a certain point of view, the architecture magnifies the micro implementation, or the underlying idea is to miniature the macro architecture. The mind of the computer is figured out, so understanding the underlying implementation can enhance the architecture capabilities, and the ability to enhance the architecture can also deepen the understanding of the underlying implementation. There is a myriad of computer knowledge, but limited routines. Grasp the foundation of several routines breakthrough, from the thinking and thinking of the angle of learning computer knowledge. Do not spend their energy on the continuous pursuit of new technology in the footsteps, follow the ' Start Guide line ' can only write a demo, the income is a demo just.
To stop and review the basics and classics may be a lot more technical.
I hope this article is helpful to everyone.