Preface:
I recently read that you can use CAS + Volatile to synchronize code blocks.
Thought, really can be realized! Because AbstractQueuedSynchronizer (AQS) through the CAS + internal volatile (modified synchronous mark a state) to realize the synchronized code block.
And ReentrantLock is based on AQS principle to achieve synchronization code blocks; ReentrantLock source code learning and understanding AQS principles can refer to: take you to explore the joy of ReentrantLock source code
Today, we will implement a mini-version of AQS via CAS + Volatile. Through this mini VERSION of AQS can make the principle of AQS more clear.
Main line of this article:
-
Description of CAS operations and volatile
-
CAS + volatile = Block synchronization (code implementation)
CAS operations and Volatile:
Learn more about why CAS operations and volatile are used to synchronize code blocks.
The CAS operation:
What is CAS?
CAS stands for compare and swap, literally compare and update; Mainly through the processor’s instructions to ensure the atomicity of the operation.
The CAS operation contains three operands:
- Memory location (V)
- Expected original value (A)
- Update the value (B)
In simple terms: take the stored value from memory location V and compare it with the expected value A. If the result is equal to the expected value A, then we update the new value B to memory location V. If not, then repeat the above operation until it succeeds.
For example, the compareAndSwapInt method in the unsafe class in the JDK:
unsafe.compareAndSwapInt(this, stateOffset, expect, update);
Copy the code
- StateOffset Specifies the memory location of the value of the stateOffset variable.
- Expect C.
- Update Update value;
Advantages of CAS:
CAS is a kind of lockless programming, is a kind of non-blocking lightweight optimistic lock; Performance is much better than the heavyweight pessimistic locks of synchronized blocking.
However, note that synchronized keyword has become very good under constant optimization (lock upgrade optimization, etc.).
Volatile keyword:
What is volatile?
Volatile is a lightweight synchronization mechanism provided by the Java Virtual machine.
What volatile does:
-
Atomicity is guaranteed for volatile variables, but not for compound operations (i++, etc.).
-
Disallow instruction reordering;
-
Variables modified by volatile are immediately perceived by other threads, ensuring visibility.
After understanding the CAS operation and the volatile keyword, you can better understand the following implementation of the synchronization code demo.
CAS + volatile = synchronize code blocks
To summarize the implementation principle of synchronous code block:
- Use the volatile keyword to modify a synchronization flag bit state of type int, with an initial value of 0.
- CAS operation is used to update the synchronization flag state during lock/lock release.
- Lock success, synchronization flag bit value is 1, lock status;
- Lock release success, synchronization flag bit value is 0, initial state;
Lock implementation:
Locking flowchart:
Lock code:
** * lock, get lock in an unfair way */public final void lock(a) {
while (true) {
// CAS operation updates the synchronization flag bit
if (compareAndSetState(0.1)) {
// Set the owner of the exclusive lock to the current thread
exclusiveOwnerThread = Thread.currentThread();
System.out.println(Thread.currentThread() + " lock success ! set lock owner is current thread . " +
"state:" + state);
try {
// Sleep for a while to simulate better effects
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Break the loop
break;
} else {
// TODO Reentrant can be set if the synchronization flag bit is 1 and the owner of the lock is the current thread, but this method is not implemented yet
if (1 == state && Thread.currentThread() == exclusiveOwnerThread) {
// Set the reentrant lock
}
System.out.println(Thread.currentThread() + " lock fail ! If the owner of the lock is the current thread," +
"The reentrant lock needs to be set; else Adds the current thread to the blocking queue .");
// Block the thread and put it on the blocking list
parkThreadList.add(Thread.currentThread());
LockSupport.park(this);
// The thread wakes up and executes here, and continues the while loop
System.out.println(Thread.currentThread() + " The currently blocking thread is awakened !"); }}}Copy the code
Lock release implementation:
Flowchart for releasing locks:
Lock release code:
/** * release lock **@return* /
public final boolean unlock(a) {
// Determine whether the owner of the lock is the current thread
if(Thread.currentThread() ! = exclusiveOwnerThread) {throw new IllegalMonitorStateException("Lock release failed ! The owner of the lock is not " +
"the current thread.");
}
// Set the synchronization flag bit to 0
state = 0;
// Set the owner of the exclusive lock to NULL
exclusiveOwnerThread = null;
System.out.println(Thread.currentThread() + " Release the lock successfully, and then wake up " +
"the thread node in the blocking queue ! The state," + state);
if (parkThreadList.size() > 0) {
// Get the blocked thread from the blocking list
Thread thread = parkThreadList.get(0);
// Wake up the blocked thread
LockSupport.unpark(thread);
// Remove the awakened thread from the blocking list
parkThreadList.remove(0);
}
return true;
}
Copy the code
The complete code is as follows:
import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.LockSupport;
/ * * *@PACKAGE_NAME: com.lyl.thread6
* @ClassName: AqsUtil
* @Description: Use CAS + volatile flag = to implement mini AQS; * <p> * <p> * Note: this class simply implements the basic unfair way of acquiring and releasing an exclusive lock; Things like reentrant locks, fair access locks, shared locks, etc. are not yet implemented * <p/> *@DateSqlstate 2021-01-15: a *@Author: [Muzi-lei] public number **/
public class AqsUtil {
/** ** Synchronization flag */
private volatile int state = 0;
/** * The exclusive lock owner */
private transient Thread exclusiveOwnerThread;
/** * The Unsafe class in the JDK provides hardware-level atomicity */
private static final Unsafe unsafe;
/** * holds a list of blocked threads */
private static List<Thread> parkThreadList = new ArrayList<>();
/** * The start address offset of the synchronization flag bit */
private static final long stateOffset;
static {
try {
unsafe = getUnsafe();
// Get the start address offset of the synchronization flag status
stateOffset = unsafe.objectFieldOffset(AqsUtil.class.getDeclaredField("state"));
} catch (NoSuchFieldException e) {
throw newError(e); }}/** * Retrieve the Unsafe object ** via reflection@return* /
private static Unsafe getUnsafe(a) {
try {
Field field = Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
return null; }}/** * get lock in an unfair way */
public final void lock(a) {
while (true) {
if (compareAndSetState(0.1)) {
// Set the owner of the exclusive lock to the current thread
exclusiveOwnerThread = Thread.currentThread();
System.out.println(Thread.currentThread() + " lock success ! set lock owner is current thread . " +
"state:" + state);
try {
// Sleep for a while to simulate better effects
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Break the loop
break;
} else {
// TODO Reentrant can be set if the synchronization flag bit is 1 and the owner of the lock is the current thread, but this method is not implemented yet
if (1 == state && Thread.currentThread() == exclusiveOwnerThread) {
// Set the reentrant lock
}
System.out.println(Thread.currentThread() + " lock fail ! If the owner of the lock is the current thread," +
"The reentrant lock needs to be set; else Adds the current thread to the blocking queue .");
// Block the thread and put it on the blocking queue
parkThreadList.add(Thread.currentThread());
LockSupport.park(this);
// The thread wakes up and executes here, and continues the while loop
System.out.println(Thread.currentThread() + " The currently blocking thread is awakened !"); }}}/** * release lock **@return* /
public final boolean unlock(a) {
if(Thread.currentThread() ! = exclusiveOwnerThread) {throw new IllegalMonitorStateException("Lock release failed ! The owner of the lock is not " +
"the current thread.");
}
// Set the synchronization flag bit to 0
state = 0;
// Set the owner of the exclusive lock to NULL
exclusiveOwnerThread = null;
System.out.println(Thread.currentThread() + " Release the lock successfully, and then wake up " +
"the thread node in the blocking queue ! The state," + state);
if (parkThreadList.size() > 0) {
// Get the blocked thread from the blocking list
Thread thread = parkThreadList.get(0);
// Wake up the blocked thread
LockSupport.unpark(thread);
// Remove the awakened thread from the blocking list
parkThreadList.remove(0);
}
return true;
}
/** * Use the CAS secure update synchronization flag **@param expect
* @param update
* @return* /
public final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update); }}Copy the code
Test run:
Test code:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/ * * *@PACKAGE_NAME: com.lyl.thread6
* @ClassName: SynCodeBlock
* @Description: Simple test *@Date: over 2021-01-15 *@Author: [Muzi-lei] public number **/
public class SynCodeBlock {
public static void main(String[] args) {
// A fixed pool of 10 threads
ExecutorService logWorkerThreadPool = Executors.newFixedThreadPool(10);
AqsUtil aqsUtil = new AqsUtil();
int i = 10;
while (i > 0) {
logWorkerThreadPool.execute(new Runnable() {
@Override
public void run(a) { test(aqsUtil); }}); --i; }}public static void test(AqsUtil aqsUtil) {
/ / lock
aqsUtil.lock();
try {
System.out.println("Business as usual.");
} finally {
/ / releases the lockaqsUtil.unlock(); }}}Copy the code
Running results:
For example, the above test program started 10 threads to execute the synchronized code block at the same time. Perhaps only thread thread-2 acquired the lock at this time, and the other threads were blocked into the blocking list because they did not acquire the lock.
When the thread that acquired the lock releases the lock, the threads in the blocking list are awakened in the order in which they entered the list. The awakened thread will try to acquire the lock again. If a new thread attempts to acquire the lock at the same time, then there is also a competition, which is an unfair way to preempt the lock (the lock is not acquired in the order in which it was acquired).
Extension:
Thread spin is not implemented in the above code, so how can it be implemented?
First, why spin is needed:
Because in some scenarios, the lock duration of synchronized resources is short, it is not worth the cost to block for this time if no thread has acquired the lock; Because the thread context is switched when entering a block, this is costly;
Spinning a thread is likely to avoid the consumption of thread context switching during blocking; In addition, the number of spins a thread has to be set to block the thread before it has acquired the lock, preventing it from wasting CPU resources by spinning continuously.
The code is as follows:
♡ like + comment + forwarding yo
If this article is helpful to you, please wave your love to make a fortune of the little hand under the praise ah, your support is my continuous creation of power, thank you!
You can wechat search [Muzilei] public number, a large number of Java learning articles, you can have a look at yo!