SoFunction
Updated on 2025-03-08

Learning non-blocking synchronization mechanism CAS

When studying the execution principle of thread pool, I saw a piece of code that kept looping and retrying. I didn't understand its principle. I read the comments. This is the implementation of CAS, so I learned to record it.

What are the disadvantages of locks

Under multi-thread concurrency, thread safety can be ensured by adding locks, but multiple threads request locks at the same time. In many cases, the operating system cannot be avoided. Thread suspension and recovery will have a lot of overhead and a long period of interruption. Some fine-grained operations, such as synchronous containers, often have very little code volume, and if locks exist and threads compete fiercely, the cost of scheduling is very high.
In summary, threads holding locks will block other threads that require locks, causing various risks and overheads. Locking is a pessimistic method. Threads always imagine that while they hold resources, there must be other threads that want resources. If they don’t lock them firmly, they can’t feel at ease.
With the support of hardware, a non-blocking synchronization mechanism has emerged, and one of the commonly used implementations is CAS.

What is CAS

Modern processors all include support for concurrency, the most common method is comparison and swap, referred to as CAS.

The CAS operation contains three operands - memory location (V), expected original value (A), and new value (B). If the value of the memory location matches the expected original value, the processor will automatically update the location value to the new value. Otherwise, the processor does nothing. Regardless of whether the V value is equal to the A value, the original value of V will be returned. CAS effectively states: I think position V should contain the value A; if it contains it, put B in this position; otherwise, don't change the position, just tell me the current value of this position.

When multiple threads try to update a variable using CAS at the same time, only one thread will succeed in the end, and the other threads will fail. But unlike using locks, the failed thread will not be blocked, but the defendant said that the update operation failed and you can try again. At this time, the thread can continue to retry or skip the operation according to the actual situation, greatly reducing the performance lost due to blockage. So, CAS is an optimistic operation, and it hopes to perform update operations successfully every time.

public class SimulationCAS {
private int value;
public synchronized int get() {
return value;
}
public synchronized boolean compareAndSet(int expectedValue, int newValue) {
if (expectedValue == compareAndSwap(expectedValue, newValue)) {
return true;
}
return false;
}
public synchronized int compareAndSwap(int expectedValue, int newValue) {
int oldValue = value;
if (oldValue == expectedValue) {
value = newValue;
}
return oldValue;
}
}

The above code simulates the operation of CAS, where compareAndSwap is the embodiment of CAS semantics, compareAndSet updates the value and returns whether it is successful or not.
Implementing CAS in a few lines of code, do you think it’s very simple? But you have to know that CAS only tells you the result of the operation. After the operation fails, a series of retry, back, back, and abandon operations must be implemented by yourself. It is much more complicated to develop than using locks.

Atom atom class

JVM supports CAS, which is reflected in our commonly used Atom atom class. Use AtomicInteger to analyze the source code.

public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}

Perform +1 operation on AtomicInteger. In the loop, the current value and the target value after +1 will be passed into compareAndSet, and the method will not be released until it is successful. Are compareAndSet very familiar? Let’s take a look at its code.

// setup to use  for updates
private static final Unsafe unsafe = ();
public final boolean compareAndSet(int expect, int update) {
return (this, valueOffset, expect, update);
}

compareAndSet is called, this is a native method, and the principle is to call the CAS method supported by the hardware. By understanding this, you should understand the principles of the Atom class. The implementation of other methods is similar.

CAS in thread pool

With the foundation of CAS, you can study the code that I haven't understood.
Submit an execution task, and the thread pool will try to add a worker thread to process the task. Here is a piece of code for addWorker in ThreadPoolExecutor:

private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! ()))
return false;
for (;;) {
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c))
break retry;
c = (); // Re-read ctl
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}

//Other omitted

In the inner loop, the compareAndIncrementWorkerCount method will be called to add a worker thread. The principle is the same as the getAndIncrement method of AtomicInteger. If the addition is successful, the loop will be directly jumped out. Otherwise, after checking the thread pool status, call compareAndIncrementWorkerCount in the inner loop again until the addition is successful.

Now look at the code and you will understand instantly.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.