To sum up, cas (compare and swap) can be understood literally. Each time, it gets the old value from a certain memory area and compares it with the expected value. If it is equal, it is modified to a new value. If it is not equal, it will spin (While(! compareAndSwap(old,current)). Here is my website for learning spin lock
https://blog.csdn.net/qq_34337272/article/details/81252853
Of course, there are two problems with spin locks:
- Because spin lock is an optimistic lock, it will remain in the optional state when the current expected value and memory value are not equal. Obviously, when the thread competition is fierce, the long-term unsuccessful spin will occupy a high cpu (which can be solved by setting the number of spins)
- Unable to re-enter. Imagine that cas will still fail when the thread holding the lock wants to acquire the lock again. (it can be solved by introducing counters)
public void lock() { Thread cur = Thread.currentThread(); if(cur == cas.get()){ //If this is the thread, the counter++ count++; return; } //Not this thread, cas gets lock while (!cas.compareAndSet(null, current)) { // DO nothing } } public void unlock() { Thread cur = Thread.currentThread(); if (cur == cas.get()) { if (count > 0) {// If it is greater than 0, it indicates that the current thread has acquired the lock for many times. Releasing the lock is simulated by count ing minus one count--; } else {// If count==0, the lock can be released, so that the number of times to obtain the lock is consistent with the number of times to release the lock. cas.compareAndSet(cur, null); } } }
The following summary comes from
https://blog.csdn.net/vanchine/article/details/111772190?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_baidulandingword-0&spm=1001.2101.3001.4242
AQS (AbstractQueuedSynchronizer) provides a set of synchronizer framework for multi-threaded access to shared resources. Many synchronization class implementations rely on it, such as ReentrantLock/Semaphore/CountDownLatch. It maintains a volatile int state. The thread determines whether the current thread successfully obtains the synchronization state (that is, cas operation on the state) by modifying (adding / subtracting the specified number) the state. At the same time, it also maintains a FIFO queue (first in first out queue). When the cas fails, the fair lock will choose to put the thread into the queue; The non fair lock will continue to spin.
Exclusive and shared locks
AQS supports two modes to obtain synchronization status: exclusive and shared. Exclusive mode allows only one thread to obtain synchronization status at the same time, while shared mode allows multiple threads to obtain synchronization status at the same time. ReentrantLock is a mutually exclusive lock implemented exclusively. The read lock of ReadWriteLock is a shared lock (obviously, reading data does not modify data, so it is not necessary to be exclusive), the write lock is an exclusive lock, and Semaphore is a shared lock (state is set to the number of accessible threads).
Take ReentrantLock as an example:
//Specify lock type public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } //Default to non fair lock public ReentrantLock() { sync = new NonfairSync(); } //Realize fair lock private ReentrantLock reentrantLock = new ReentrantLock(true);
In terms of Synchronized, it is an unfair lock. Unlike ReentrantLock, which implements thread scheduling through AQS, there is no way to turn it into a fair lock.
The following is an analysis of some of the ReentranLock source code:
ReentrantLock lock = new ReentrantLock(); lock.lock(); lock.unlock(); --FairSync //Fair competition in lock final void lock() { //All compete for locks according to FIFO acquire(1); } --UnFairSync //Competitive lock in unfair lock final void lock() { ยท //Any thread can preempt the lock through CAS first if (compareAndSetState(0, 1)) //Set exclusive lock setExclusiveOwnerThread(Thread.currentThread()); else //If preemption fails, the lock is competed according to FIFO acquire(1); } --AbstractQueuedSynchronizer //Releasing the lock is an implementation public final boolean release(int arg) { if (tryRelease(arg)) { // Node is a node that contains thread information and is placed in the queue Node h = head; if (h != null && h.waitStatus != 0) //Release pending status unparkSuccessor(h); return true; } return false; }
The acquire method comes from AQS implementation:
public final void acquire(int arg) { //tryAcquire can be understood as an attempt to obtain an exclusive lock directly //After tryAcquire fails, add the encapsulated Node of the current thread to the end of AQS queue (addWaiter) ///And continue to cycle to acquire locks (acquirequeueueueueueueueued) if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg) ) selfInterrupt(); } --FairSync protected final boolean tryAcquire(int acquires) { ... final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //hasQueuedPredecessors() first determines whether there are head and tail nodes //Then judge whether there is a successor node, and finally judge whether the successor node is the current thread // hasQueuedPredecessors() returns False when judging success //return h != T & & whether there are head and tail nodes //((s = h.next) == null | | whether there is a successor node // s.thread != Thread.currentThread() ); Is it the current thread //If yes, CAS needs to be continued if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } ... } --UnFairSync final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); //Get the status value. If it is 0, it means that CAS can still be used again if (c == 0) { if (compareAndSetState(0, acquires)) { //Sets the thread that currently holds the lock setExclusiveOwnerThread(current); return true; } } //If the status is not 0, continue to judge whether the thread obtaining the lock is the current thread else if (current == getExclusiveOwnerThread()) { //If yes, add 1 to status to increase the number of reentries int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } //If none, return false return false; } //If TryAcquire fails, it needs to be added to the end of the queue final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { //Get the prev (precursor) node of the current node first final Node p = node.predecessor(); //If the precursor node is head, try to acquire the lock again if (p == head && tryAcquire(arg)) { //After obtaining the lock, set the head to the current node and clear the thread information and prev of the node setHead(node); p.next = null; // help GC failed = false; return interrupted; } //Failed to acquire lock. Judge whether to suspend if (shouldParkAfterFailedAcquire(p, node) && //The status of the precursor node is SIGNAL, and the current thread park is blocked //LockSupport.park/unpark provides a switch like function for threads parkAndCheckInterrupt()) interrupted = true; } } finally { if (failed) //To cancel the lock acquisition is to set the waitStatus of the node to cancelled and clear the contents of the node //The most important thing is that unpark is released for the next thread cancelAcquire(node); } }