1. Why use non-blocking algorithms?
We know that in order to avoid the problem of operating shared variables in a concurrent environment, thread safety can be done in the same way as synchronous (synchronize) and lock (locks), but the JVM handles the lock contention for the thread that failed to compete, and the policy that was later dispatched. This leads to additional thread context switching costs. At the same time as the CAS (Compare and Set) non-blocking algorithm, CAS is implemented at the underlying hardware (CPU) level, requiring only a separate memory location, and finer granularity of synchronization so that CAs failed threads can retry immediately without suspending. In general, most scenarios have better performance compared to non-blocking algorithms and synchronous locks, and minimizing the granularity of serial code is the key to parallel performance.
2. Drawbacks of non-blocking algorithms
The CAS algorithm is first compare and set, if the compare result is false, then the set will not be executed directly return false, that is, the non-blocking algorithm may fail, so the non-blocking algorithm will often need to put in the loop repeatedly retry until successful, So when the lock competition is very serious, the performance of the non-blocking algorithm can be severely degraded, even less than the blocking algorithm.
3. The simplest CAS usage
The full name of CAS is compare and set, which is a CPU atomic semantic operation supported by the modern mainstream CPU, which is simply to compare with a certain value, if equal to this value is set to a new value, if not equal to this value will discard the operation and return failure. Here we look at the most commonly used counters, first we look at a single-threaded counter:
public class Singlethreadcounter implements Counter { @Override public void Increment () { this.counter++;< c3/>} @Override public int Getcounter () { return counter; } private int counter = 0;}
This counter is completely free from single thread, but once there are multiple threads executing increment, there will be a problem, because counter++ is the first value, then the assignment, if the A thread takes out the 1,b thread is also removed 1,a set counter to 2, B After setting the counter is also 2, finally B has covered the results of a.
Next we look at the synchronized counter
public class Synchronizedcounter implements Counter { private volatile int Counter = 0; public synchronized void increment () { counter = counter + 1; } @Override public int Getcounter () { return counter; }}
This counter solves the problem of the previous multithreaded concurrency increment, because the increment method is synchronous and there are no two threads performing increment operations at the same time, in fact the increment operation is serial. This approach ensures thread safety, but at the expense of concurrency.
Finally, we look at the counter of the lock-free algorithm
public class Cascounter implements Counter { private Atomicinteger Counter = new Atomicinteger (0); @Override public void Increment () {for (;;) { int cur = counter.get (); int next = cur + 1; if (Counter.compareandset (cur, next)) { return;}} } @Override public int Getcounter () { return counter.get ();} }
We look at the increment method in this class, in fact this method and the Atomicinteger Incrementandget method is the same, is to do a CAS operation in each cycle, first take out the current value cur, and then CAs (cur,cur+1) , if the return succeeds if the update succeeds, then return directly; If the failure indicates that the other thread has changed the value of the counter, continue looping through the previous operation until it succeeds. It is important to note that the underlying variable returned by the Counter.get method is a volatile value that guarantees the visibility of the memory, so there is no problem reading the dirty value.
4. Summary
Compared with synchronous lock, the CAS algorithm reduces the serial granularity from the method level to the CPU's CAS atomic operation, improves the concurrency, and therefore increases the capacity of the system, but the CAs are retried when they fail, so if the concurrency is very high and the competition is very serious, because of the large number of retry operations, CAS may not even be as efficient as synchronization methods.
In the next section we will continue to talk about how to use CAS algorithms in some more complex data structures.
Non-blocking algorithms-Simple counters