CAS
summary
The full name of CAS is compare and swap, which is a CPU concurrency primitive,
- Compare whether the working memory value (expected value) and the shared value of the main physical memory are the same. If they are the same, perform the specified operation. Otherwise, continue the comparison until the values of the main memory and the working memory are consistent. This process is atomic
- AtomicInteger class mainly uses CAS(compare and swap)+volatile and native methods to ensure atomic operations, so as to avoid the high overhead of synchronized and greatly improve the execution efficiency
The CAS concurrency primitive embodied in the Java language is sun Each method in the UnSafe class under misc package Call the CAS method in the UnSafe class, and the JVM will help me implement the CAS assembly instruction
-
This is a completely hardware dependent function through which atomic operation is realized
-
CAS is a system primitive, which belongs to the category of operating system. It is a process composed of several instructions to complete a function, and the execution of the primitive must be continuous. Interruption is not allowed in the execution process, that is to say, CAS is an atomic instruction and will not cause the so-called problem of data inconsistency
-
Examples, such as the getAndIncrement method in unSafe
-
public final boolean compareAndSet(int expect, int update): if the current status value is equal to the expected value, set the synchronization status to the given update value in atomic mode.
/* * CAS:Compare and swap [Compare and exchange] * */ public class AtomicIntegerDemo { public static void main(String[] args) { //The current status value is 5 AtomicInteger atomicInteger=new AtomicInteger(5); //Current status value: 5; Expected value: 5, updated value: 2019 //Result: true 2019 System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t"+atomicInteger.get()); //Current status value: 2019; Expected value: 5, updated value: 2022 //Result: false 2019 (update failed, so false is returned) System.out.println(atomicInteger.compareAndSet(5, 2222)+"\t"+atomicInteger.get()); } }
UnSafe class
UnSafe class is the core class of CAS. Because Java methods cannot directly access the bottom layer, they need to be accessed through native methods. UnSafe is equivalent to a back door. Based on this class, specific memory data can be directly operated
-
The UnSafe class is sun In misc package, its internal method operation can directly operate memory like the pointer of C, because CAS operation in Java depends on the method of UnSafe class
-
All the methods in the UnSafe class are modified native ly, that is, the methods in the UnSafe class directly call the underlying resources to execute the response task
-
public final int getAndIncrement:
The variable ValueOffset is the offset address of the variable in memory, because UnSafe obtains data according to the memory offset address -
Variable value and volatile are decorated to ensure the visibility between multiple threads
atomic
Atomic is an atomic class, mainly including the following
The Java development manual describes:
Basic type atomic class (AtomicInteger, AtomicBoolean, AtomicLong)
method | explain |
---|---|
public final int get() | Gets the current value |
public final int getAndSet(int newValue) | Gets the current value and sets a new value |
public final int getAndIncrement() | Gets the current value and increments it automatically |
public final int getAndDecrement() | Get the current value and subtract from it |
public final int getAndAdd(int delta) | Gets the current value and adds the expected value |
public final int incrementAndGet( ) | Returns the value after adding 1 |
boolean compareAndSet(int expect,int update) | Returns true if the entered value is equal to the expected value |
-
AtomicInteger solves the unsafe problem of i + + multithreading
The code is as follows:public class AtomicIntegerDemo { AtomicInteger atomicInteger=new AtomicInteger(0); public void addPlusPlus(){ atomicInteger.incrementAndGet(); } public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch=new CountDownLatch(10); AtomicIntegerDemo atomic=new AtomicIntegerDemo(); // 10 threads call addPlusPlus 100 times in a loop, and the final result is 10 * 100 = 1000 for (int i = 1; i <= 10; i++) { new Thread(()->{ try{ for (int j = 1; j <= 100; j++) { atomic.addPlusPlus(); } }finally { countDownLatch.countDown(); } },String.valueOf(i)).start(); } //(1). If the following pause time of 3 seconds is not added, the main thread will end before i++ 1000 times //try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();} //(2). Use CountDownLatch to solve the problem of waiting time countDownLatch.await(); System.out.println(Thread.currentThread().getName()+"\t"+"Obtained result:"+atomic.atomicInteger.get()); } }
CountDownLatch is a synchronization tool class, which is used to coordinate the synchronization between multiple threads or talk about the communication between threads (rather than being used as mutual exclusion).
- CountDownLatch enables a thread to wait for other threads to complete their work before continuing execution. It is implemented using a counter. The initial value of the counter is the number of threads. When each thread completes its task, the value of the counter will be reduced by one. When the counter value is 0, it means that all threads have completed some tasks, and then the threads waiting on CountDownLatch can resume executing the next tasks.
-
AtomicBoolean can be used as an interrupt identifier to stop a thread
//Implementation of thread interrupt mechanism public class AtomicBooleanDemo { public static void main(String[] args) { AtomicBoolean atomicBoolean=new AtomicBoolean(false); new Thread(()->{ System.out.println(Thread.currentThread().getName()+"\t"+"coming....."); while(!atomicBoolean.get()){ System.out.println("=========="); } System.out.println(Thread.currentThread().getName()+"\t"+"over....."); },"A").start(); new Thread(()->{ atomicBoolean.set(true); },"B").start(); } }
-
The underlying idea of AtomicLong is CAS + spin lock, which is applicable to global computing with low concurrency. After high concurrency, the performance drops sharply. The reasons are as follows: n threads modify the value of CAS, and only one thread succeeds each time. Other N-1 fails. The failed spins continuously until it succeeds. In this case, a large number of failed spins, the cpu is hit high all at once (AtomicLong's spin will become a bottleneck)
In the case of high concurrency, we use LoadAdder
Array type atomic class (AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray)
Array type atomic class, mainly including three AtomicIntegerArray, AtomicLongArray and AtomicReferenceArray
public class AtomicIntegerArrayDemo { public static void main(String[] args) { //(1). Create a new AtomicIntegerArray with the same length as all elements copied from the given array. int[]arr2={1,2,3,4,5}; AtomicIntegerArray array=new AtomicIntegerArray(arr2); //(2). Creates a new AtomicIntegerArray of the given length, with all elements initially zero. //AtomicIntegerArray array=new AtomicIntegerArray(5); for (int i = 0; i < arr.length; i++) { System.out.print(arr[i]); } System.out.println(); System.out.println("======="); array.getAndSet(0,1111); System.out.println("============"); System.out.println("Change the element at position 0 in the number to:"+array.get(0)); System.out.println("The array position is 1. The old value at position is:"+array.get(1)); System.out.println("Add 1 to the number at the position where the array position is 1"); array.getAndIncrement(1); System.out.println("The array position is 1. The new value at position is:"+array.get(1)); } }
Reference type atomic class (AtomicReference, AtomicStampedReference, AtomicMarkableReference)
There are three atomic classes of reference types: AtomicReference, AtomicStampedReference, and AtomicMark ableReference
-
Use AtomicReference to implement the spin lock case
//Spin lock public class AtomicReferenceThreadDemo { static AtomicReference<Thread>atomicReference=new AtomicReference<>(); static Thread thread; public static void lock(){ thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"\t"+"coming....."); while(!atomicReference.compareAndSet(null,thread)){ } } public static void unlock(){ System.out.println(Thread.currentThread().getName()+"\t"+"over....."); atomicReference.compareAndSet(thread,null); } public static void main(String[] args) { new Thread(()->{ AtomicReferenceThreadDemo.lock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();} AtomicReferenceThreadDemo.unlock(); },"A").start(); new Thread(()->{ AtomicReferenceThreadDemo.lock(); AtomicReferenceThreadDemo.unlock(); },"B").start(); } }
-
AtomicStampedReference solves ABA problems
The reference type atomic class with version number can solve the ABA problem
The solution has been modified several times
State stamp atomic reference -
AtomicMarkableReference does not recommend using it to solve ABA problems
Atomic update reference type object with flag bit
Whether to modify (its definition is to simplify the status stamp to the bit true|false), similar to disposable chopsticks
Status stamp (true/false) atomic reference
It is not recommended to use it to solve ABA problems -
Differences between AtomicStampedReference and AtomicMarkableReference
- stamped – version number, modified once + 1
- Markable – whether true and false have been modified
Object (AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, AtomicReferenceFieldUpdater)
Manipulate certain fields within a non thread safe object in a thread safe manner
Can you not lock the whole object, reduce the scope of locking, and only focus on a field with long-term and sensitive changes, rather than the whole object, so as to achieve the purpose of precise locking + memory saving. That is, lock only one operation
Requirements for modifying atomic classes using object properties:
-
Updated object properties must use the public volatile modifier
-
Because the property modification types of objects are abstract classes, you must use the static method newUpdater() to create an updater every time you use it, and you need to set the classes and properties you want to update. As follows:
-
Example: we use AtomicIntegerFieldUpdater to set the int element in the Score object as an atomic object.
/*** 1.From the AtomicIntegerFieldUpdaterDemo code, it is not difficult to find that when we update the score through AtomicIntegerFieldUpdater, we do not need to call the get() method to obtain the final int value compared with AtomicInteger! 2.For AtomicIntegerFieldUpdaterDemo class, AtomicIntegerFieldUpdater is of static final type, that is, even if 100 objects are created, there is only one AtomicIntegerField that will not occupy the memory of the object, but AtomicInteger will create multiple AtomicInteger objects, which will occupy more memory than AtomicIntegerFieldUpdater, Therefore, people familiar with dubbo's source code know that dubbo has a class AtomicPositiveInteger that implements polling load balancing strategy, which uses AtomicIntegerFieldUpdater. */ @SuppressWarnings("all") public class AtomicIntegerFieldUpdaterDemo { private static final int THREAD_NUM = 1000; //The fence is set to prevent the main thread from outputting self increasing variables before the loop ends, resulting in the mistaken belief that the thread is unsafe private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_NUM); Score score=new Score(); public static void main(String[] args)throws InterruptedException { Score score = new Score(); for (int j = 0; j < THREAD_NUM; j++) { new Thread(() -> { score.addTotalScore(score); countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println("totalScore Value of:" + score.totalScore); } } class Score { String username; public volatile int totalScore = 0; //public static <U> AtomicIntegerFieldUpdater<U> newUpdater(Class<U> tclass,String fieldName) private static AtomicIntegerFieldUpdater atomicIntegerFieldUpdater = AtomicIntegerFieldUpdater.newUpdater(Score.class, "totalScore"); public void addTotalScore(Score score){ //public int incrementAndGet(T obj) { atomicIntegerFieldUpdater.incrementAndGet(score); } }
-
AtomicReferenceFieldUpdater: updates the value of the atomic reference type field
Example: multiple threads call the initialization method of a class concurrently. If it has not been initialized, the initialization will be executed. It is required that it can only be initialized once.//Requirements: multiple threads call the initialization method of a class concurrently. If it has not been initialized, the initialization will be performed. It is required that it can only be initialized once public class AtomicReferenceFieldUpdaterDemo { public static void main(String[] args) { MyCar myCar=new MyCar(); AtomicReferenceFieldUpdater<MyCar,Boolean>atomicReferenceFieldUpdater= AtomicReferenceFieldUpdater.newUpdater(MyCar.class,Boolean.class,"flag"); for (int i = 1; i <= 5; i++) { new Thread(()->{ if(atomicReferenceFieldUpdater.compareAndSet(myCar,Boolean.FALSE,Boolean.TRUE)){ System.out.println(Thread.currentThread().getName()+"\t"+"---init....."); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) {e.printStackTrace();} System.out.println(Thread.currentThread().getName()+"\t"+"---init.....over"); }else{ System.out.println(Thread.currentThread().getName()+"\t"+"------Another thread is initializing"); } },String.valueOf(i)).start(); } } } class MyCar{ public volatile Boolean flag=false; }
-
Reflections on the use of AtomicIntegerFieldUpdater and AtomicInteger
Through the following code, it is not difficult for us to know that the effect of using AtomicIntegerFieldUpdater is consistent with that of AtomicInteger. Since the God of AtomicInteger concurrency already exists, what about writing an AtomicIntegerFieldUpdater?- From the AtomicIntegerFieldUpdaterDemo code, it is not difficult to find that when we update the score through AtomicIntegerFieldUpdater, we do not need to call the get() method to obtain the final int value compared with AtomicInteger!
- For the AtomicIntegerFieldUpdaterDemo class, the AtomicIntegerFieldUpdater is of static final type, that is, even if 100 objects are created, there is only one AtomicIntegerField that will not occupy the memory of the object
- However, AtomicInteger creates multiple AtomicInteger objects, which takes up more memory than AtomicIntegerFieldUpdater
- Therefore, people familiar with dubbo's source code know that dubbo has a class that implements polling load balancing strategy. AtomicPositiveInteger uses AtomicIntegerFieldUpdate, which is widely used at the bottom of netty
public static class Candidate { int id; volatile int score = 0; AtomicInteger score2 = new AtomicInteger(); } public static final AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score"); public static AtomicInteger realScore = new AtomicInteger(0); public static void main(String[] args) throws InterruptedException { final Candidate candidate = new Candidate(); Thread[] t = new Thread[10000]; for (int i = 0; i < 10000; i++) { t[i] = new Thread() { @Override public void run() { if (Math.random() > 0.4) { candidate.score2.incrementAndGet(); scoreUpdater.incrementAndGet(candidate); realScore.incrementAndGet(); } } }; t[i].start(); } for (int i = 0; i < 10000; i++) { t[i].join(); } System.out.println("AtomicIntegerFieldUpdater Score=" + candidate.score); System.out.println("AtomicInteger Score=" + candidate.score2.get()); System.out.println("realScore=" + realScore.get()); } } /** AtomicIntegerFieldUpdater Score=5897 AtomicInteger Score=5897 realScore=5897 */
Atomic operation enhancement classes (DoubleAccumulator, DoubleAdder, LongAccumulator, LongAdder)
method | explain |
---|---|
void add(long x) | Add 1 to the current value |
void increment( ) | Add 1 to the current value |
void decrement( ) | Subtract the current value by 1 |
long sum( ) | Returns the current value, especially if value is not updated concurrently. |
long longvale | Equivalent to long sum(). |
long sumThenReset() | Get the current value and reset value to 0 |
-
LongAdder and LongAccumulato
LongAdder can only be used to calculate addition and subtraction, and starts from zero
The LongAccumulator provides custom function operationspublic class LongAdderDemo { public static void main(String[] args) { // LongAdder can only do addition and subtraction, not multiplication and division LongAdder longAdder=new LongAdder(); longAdder.increment(); longAdder.increment(); longAdder.increment(); longAdder.decrement(); System.out.println(longAdder.longValue()); System.out.println("========"); //LongAccumulator(LongBinaryOperator accumulatorFunction, long identity) //LongAccumulator longAccumulator=new LongAccumulator((x,y)->x+y,0); LongAccumulator longAccumulator=new LongAccumulator(new LongBinaryOperator() { @Override public long applyAsLong(long left, long right) { return left*right; } },5); longAccumulator.accumulate(1); longAccumulator.accumulate(2); longAccumulator.accumulate(3); System.out.println(longAccumulator.longValue()); } }
-
LongAdder high performance comparison code demo
import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.LongAccumulator; import java.util.concurrent.atomic.LongAdder; class ClickNumber { int number=0; //(1). Implementing number using synchronized++ public synchronized void add_synchronized(){ number++; } //(2). Using AtomicInteger AtomicInteger atomicInteger=new AtomicInteger(); public void add_atomicInteger(){ atomicInteger.incrementAndGet(); } //(3). Using AtomicLong AtomicLong atomicLong=new AtomicLong(); public void add_atomicLong(){ atomicLong.incrementAndGet(); } //(4). Using LongAdder LongAdder adder=new LongAdder(); public void add_longAdder(){ adder.increment(); } //(5). Using the longaccumulator LongAccumulator accumulator=new LongAccumulator((x, y)->x+y,0); public void add_longAccumulater(){ accumulator.accumulate(1); } } /** * 50 Threads, 100w times per thread, total likes * */ public class LongAdderCalcDemo { // 50 threads and each thread point at 100w times public static final int SIZE_THREAD=50; public static final int _1w=10000; public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch=new CountDownLatch(SIZE_THREAD); ClickNumber clickNumber=new ClickNumber(); long startTime = System.currentTimeMillis(); for (int i = 1 ; i <=SIZE_THREAD ; i++) { new Thread(()->{ try{ for (int j = 1; j <=10*_1w; j++) { //We can clearly see that calling LongAdder performs best //clickNumber.add_synchronized(); clickNumber.add_longAdder(); } }finally { countDownLatch.countDown(); } },String.valueOf(i)).start(); } countDownLatch.await(); long endTime = System.currentTimeMillis(); System.out.println("-----consTime:"+(endTime-startTime)+"millisecond"+"\t"); System.out.println(clickNumber.adder.longValue()); } }
-
The results show that
// clickNumber. add_ Result of longadder() -----consTime:107 millisecond 5000000 // clickNumber.add_synchronized(); Results -----consTime:447 millisecond 5000000
Disadvantages of CAS
- Long cycle time and high overhead
We can see that when the getAndInt method executes, there is a do while
If CAS fails, it will keep trying. If CAS fails for a long time, it may bring great overhead to CPU
- The atomicity of one shared variable can be guaranteed, but the atomicity of multiple shared variables cannot be guaranteed.
When operating on a shared variable, we can use cyclic CAS to ensure atomic operation
When operating on multiple shared variables, cyclic CAS cannot guarantee the atomicity of the operation. At this time, locks can be used to ensure the atomicity
ABA problem
Cause
Suppose there are two threads, T1 and T2, respectively. Then T1 executes an operation for 10 seconds and T2 executes an operation for 2 seconds. At first, AB threads obtain the value of A from the main memory respectively. However, because B executes faster, he first changes the value of A to B, then modifies it to A, and then completes the execution. T1 thread completes the execution after 10 seconds, Judging that the value in the memory is A, and it is the same as its expected value, it thinks that no one has changed the value in the main memory and happily modifies it to B, but in fact, it may have experienced the transformation of ABCDEFA in the middle, that is, the middle value has experienced the transformation of civet cat for prince.
import java.util.concurrent.atomic.AtomicInteger; public class ABADemo { static AtomicInteger atomicInteger = new AtomicInteger(100); public static void main(String[] args) { new Thread(() -> { atomicInteger.compareAndSet(100, 101); atomicInteger.compareAndSet(101, 100); }, "t1").start(); new Thread(() -> { //Pause the thread for a moment try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } ; System.out.println(atomicInteger.compareAndSet(100, 2019) + "\t" + atomicInteger.get()); }, "t2").start(); } }
result:
true 2019
Therefore, the ABA problem is that when obtaining the main memory value, the memory value has been modified N times when we write it into the main memory, but it has finally been changed to the original value. Although the result is no problem, when thread B judges, atomoicInteger has actually been modified. If we use the result of B to judge whether atomoicInteger has been modified, we can't get the correct answer.
ABA problem solution
-
①. The solution to the ABA problem is to increase the version number: each modification of the AtomicStampedReference will have a version number
-
②. Note: AtomicStampedReference is used to solve the ABA problem in AtomicInteger. This demo attempts to increase the value of integer from 0 to 1000, but it will stop growing when the value of integer increases to 128. There are two reasons for this phenomenon:
- Use the int type instead of Integer to save the current value
- Interger's cache of - 128 ~ 127 [this range is valid. If it is not in this range, comareAndSet will always return false
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1); public static void main(String[] args) { new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t First version number"+stamp+"\t Value is"+stampedReference.getReference()); //Pause t3 thread for 1 second try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t Second version number"+stampedReference.getStamp()+"\t Value is"+stampedReference.getReference()); stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1); System.out.println(Thread.currentThread().getName()+"\t 3rd version number"+stampedReference.getStamp()+"\t Value is"+stampedReference.getReference()); },"t3").start(); new Thread(()->{ int stamp = stampedReference.getStamp(); System.out.println(Thread.currentThread().getName()+"\t First version number"+stamp+"\t Value is"+stampedReference.getReference()); //Ensure that thread 3 completes ABA once try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1); System.out.println(Thread.currentThread().getName()+"\t Modified successfully"+result+"\t Latest version number"+stampedReference.getStamp()); System.out.println("Latest value\t"+stampedReference.getReference()); },"t4").start(); } }
Six locks
Optimistic lock and pessimistic lock
Pessimistic lock
- The synchronized keyword and the Lock implementation class are pessimistic locks
- What is a pessimistic lock? I think there must be another thread to modify the data when I use the data, so I will lock it first when I get the data to ensure that the data will not be modified by other threads=
- It is suitable for scenarios with many write operations. Locking first can ensure that the data is correct during write operations (write operations include addition, deletion and modification), and explicit locking before operating synchronous resources
Optimistic lock
Optimistic lock thinks that no other thread will modify the data when using the data, so it will not add the lock. It just judges whether other threads have updated the data before updating the data.
- If the data is not updated, the current thread successfully writes the modified data.
- If the data has been updated by other threads, different operations are performed according to different implementation methods
- Optimistic locking is implemented by using lock free programming in Java. The most commonly used is CAS algorithm. The increment operation in Java atomic class is realized by CAS spin
It is suitable for scenarios with many read operations. The feature of no lock can greatly improve the performance of read operations
Optimistic locks are generally implemented in two ways (using version number mechanism and CAS algorithm)
example:
//Call mode of pessimistic lock public synchronized void m1(){ //Business logic after locking } //Ensure that multiple threads use the same lock object ReetrantLock lock=new ReentrantLock(); public void m2(){ lock.lock(); try{ //Operation synchronization resource }finally{ lock.unlock(); } } //Optimistic lock calling method //Ensure that multiple threads use the same AtomicInteger private AtomicInteger atomicIntege=new AtomicInteger(); atomicIntege.incrementAndGet();
Fair lock and unfair lock
concept
-
Fair lock: it means that multiple threads acquire locks in the order of applying for locks, similar to queuing for rice first come first served
-
Unfair lock: it means that the order in which multiple threads acquire locks is not the order in which they apply for locks. It is possible that the thread that applies later obtains locks first than the thread that applies first. In the case of high concurrency, it may cause priority reversal or starvation
Note: synchronized and ReentrantLock are non fair locks by default -
Example: case of queuing for tickets (fair lock hunger)
Lock starvation: we use five threads to buy 100 tickets. The ReentrantLock defaults to an unfair lock. The results obtained may be that thread A is selling these 100 tickets, which will lead to lock starvation in threads B, C, D and Eclass Ticket { private int number = 50; private Lock lock = new ReentrantLock(true); //By default, the non fair lock is used. The average distribution is a little bit, and = -- -- "fair" is a little bit public void sale() { lock.lock(); try { if(number > 0) { System.out.println(Thread.currentThread().getName()+"\t Sell No: "+(number--)+"\t Remaining: "+number); } }finally { lock.unlock(); } } /*Object objectLock = new Object(); public void sale(){ synchronized (objectLock) { if(number > 0) { System.out.println(Thread.currentThread().getName()+"\t Sold number: "+ (number -- +" \ tthe remaining: "+ number); } } }*/ } public class SaleTicketDemo { public static void main(String[] args) { Ticket ticket = new Ticket(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"a").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"b").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"c").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"d").start(); new Thread(() -> { for (int i = 1; i <=55; i++) ticket.sale(); },"e").start(); } }
Source code analysis
- Fair lock: sort queue fair lock is to judge whether there are pioneer nodes in the synchronization queue. If there is no pioneer node, it can be locked
- The first to occupy, first to get, unfair lock doesn't matter. It can run as long as it can grab the synchronization state
ReentrantLock is a non fair lock by default. A fair lock requires one more method, so the performance of a non fair lock is better (aqs source code)
Why are there fair locks and unfair locks? Why is it unfair by default
- There is still a time difference between restoring the suspended thread and obtaining the real lock. From the perspective of developers, this time is very small, but from the perspective of CPU, this time is still obvious. Therefore, unfair lock can make full use of the CPU time slice and minimize the CPU idle state time
- When using multithreading, an important consideration is the overhead of thread switching. When using an unfair lock, when a thread requests the lock to obtain the synchronization state and then releases the synchronization state, because it does not need to consider whether there is a precursor node, the probability that the thread that just released the lock will obtain the synchronization state again becomes very large, so the overhead of the thread is reduced
When to use fair? When to use unfair?
For higher throughput, it is obvious that unfair locking is more appropriate, because it saves a lot of thread switching time, and the throughput naturally goes up. Otherwise, use the fair lock and everyone will use it fairly
Reentrant lock (also known as recursive lock)
concept
Reentrant lock:
- Yes: Yes
- Heavy: again
- Enter: enter, enter what: enter the synchronization domain (i.e. synchronization code block, method or code showing lock locking)
- Locks: synchronous locks
Reentrant lock, also known as recursive lock, means that when the same thread acquires a lock in the outer method, the inner method of the thread will automatically acquire the lock (provided that the lock object must be the same object) and will not be blocked because it has been acquired before and has not been released
- If it is a recursive call method decorated with synchronized, and the second entry of the program is blocked by itself, isn't it a big joke and self binding. Therefore, ReentrantLock and synchronized in Java are reentrant locks. One advantage of reentrant locks is that deadlocks can be avoided to a certain extent
The code verifies that synchronized is a reentrant lock
//synchronized is a reentrant lock class Phone{ public synchronized void sendSms() throws Exception{ System.out.println(Thread.currentThread().getName()+"\tsendSms"); sendEmail(); } public synchronized void sendEmail() throws Exception{ System.out.println(Thread.currentThread().getName()+"\tsendEmail"); } } /** * Description: * Reentrant locks (also called recursive locks) * It refers to the code that the inner hostile function can still obtain the lock after the same outer function obtains the lock * When the outer method of the same thread acquires a lock, it will automatically acquire the lock when entering the inner method * * That is, a thread can enter any block of code synchronized with the lock it has marked * **/ public class ReenterLockDemo { /** * t1 sendSms * t1 sendEmail * t2 sendSms * t2 sendEmail * @param args */ public static void main(String[] args) { Phone phone = new Phone(); new Thread(()->{ try { phone.sendSms(); } catch (Exception e) { e.printStackTrace(); } },"t1").start(); new Thread(()->{ try { phone.sendSms(); } catch (Exception e) { e.printStackTrace(); } },"t2").start(); } }
ReentrantLock is a reentrant lock
//ReentrantLock is a reentrant lock class Phone implements Runnable { private Lock lock = new ReentrantLock(); @Override public void run() { get(); } private void get() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\tget"); set(); } finally { lock.unlock(); } } private void set() { lock.lock(); try { System.out.println(Thread.currentThread().getName() + "\tset"); } finally { lock.unlock(); } } } /** * Description: * Reentrant locks (also called recursive locks) * It refers to the code that the inner hostile function can still obtain the lock after the same outer function obtains the lock * When the outer method of the same thread acquires a lock, it will automatically acquire the lock when entering the inner method * <p> * That is, a thread can enter any block of code synchronized with the lock it has marked **/ public class ReenterLockDemo { /** * Thread-0 get * Thread-0 set * Thread-1 get * Thread-1 set */ public static void main(String[] args) { Phone phone = new Phone(); Thread t3 = new Thread(phone); Thread t4 = new Thread(phone); t3.start(); t4.start(); } }
Types of reentrant locks
- The implicit lock (that is, the lock used by the synchronized keyword) is a reentrant lock by default, which is used in the synchronization block and synchronization method
When you call other synchronized modified methods or code blocks of this class inside a synchronized modified method or code block, you can always get a lock - Display locks (i.e. locks) also have reentrant locks such as ReentrantLock
lock and unlock must match one by one. If there is less or more, it will pit other threads
Implementation mechanism of Synchronized reentry
Implementation mechanism of Synchronized reentry (why can any object become a lock)
- Each lock object has a lock counter and a pointer to the thread holding the lock
- When the monitorenter is executed, if the counter of the target lock object is zero, it indicates that it is not held by other threads. The Java virtual opportunity sets the holding thread of the lock object as the current thread and increases the counter by 1
- When the counter of the target lock object is not zero, if the holding thread of the lock object is the current thread, the Java virtual machine can increase its counter by 1, otherwise it needs to wait until the holding thread releases the lock. The number of this counter can also be called the number of resulting reentries.
- When monitorexit is executed, the Java virtual machine needs to decrease the counter of the lock object by 1. A zero counter indicates that the lock has been released
Deadlock and troubleshooting
Deadlock refers to the phenomenon that two or more threads wait for each other due to competing for resources during execution. Without external interference, they will not be able to move forward. If resources are sufficient and the resource requests of the process can be met, the possibility of deadlock is very low, otherwise they will fall into deadlock due to competing for limited resources
Causes of deadlock
-
Insufficient system resources
-
The sequence of process running and advancing is inappropriate
-
Improper allocation of resources
-
Examples are as follows:
public class DeadLockDemo{ static Object lockA = new Object(); static Object lockB = new Object(); public static void main(String[] args){ Thread a = new Thread(() -> { synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "\t" + " Own holding A Lock, expect to get B lock"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "\t get B Lock successful"); } } }, "a"); a.start(); new Thread(() -> { synchronized (lockB){ System.out.println(Thread.currentThread().getName()+"\t"+" Own holding B Lock, expect to get A lock"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockA){ System.out.println(Thread.currentThread().getName()+"\t get A Lock successful"); } } },"b").start(); } }
-
Deadlock elimination scheme 1: jstack process number
As follows:D:\studySoft\Idea201903\JavaSelfStudy>jps 10048 Launcher 6276 DeadLockDemo 6332 Jps 9356 D:\studySoft\Idea201903\JavaSelfStudy>jstack 6276 (The last one found a deadlock) 2021-07-28 16:05:36 Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.111-b14 mixed mode): "DestroyJavaVM" #16 prio=5 os_prio=0 tid=0x0000000003592800 nid=0x830 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "b" #15 prio=5 os_prio=0 tid=0x00000000253d5000 nid=0x1ba8 waiting for monitor entry [0x0000000025c8e000] java.lang.Thread.State: BLOCKED (on object monitor) at com.xiaozhi.juc.DeadLockDemo.lambda$main$1(DeadLockDemo.java:31) - waiting to lock <0x0000000741404050> (a java.lang.Object) - locked <0x0000000741404060> (a java.lang.Object) at com.xiaozhi.juc.DeadLockDemo$$Lambda$2/2101440631.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "a" #14 prio=5 os_prio=0 tid=0x00000000253d3800 nid=0xad8 waiting for monitor entry [0x0000000025b8e000] java.lang.Thread.State: BLOCKED (on object monitor) at com.xiaozhi.juc.DeadLockDemo.lambda$main$0(DeadLockDemo.java:20) - waiting to lock <0x0000000741404060> (a java.lang.Object) - locked <0x0000000741404050> (a java.lang.Object) at com.xiaozhi.juc.DeadLockDemo$$Lambda$1/1537358694.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "Service Thread" #13 daemon prio=9 os_prio=0 tid=0x000000002357b800 nid=0x1630 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C1 CompilerThread3" #12 daemon prio=9 os_prio=2 tid=0x00000000234f6000 nid=0x1fd4 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread2" #11 daemon prio=9 os_prio=2 tid=0x00000000234f3000 nid=0x5c0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread1" #10 daemon prio=9 os_prio=2 tid=0x00000000234ed800 nid=0x1afc waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "C2 CompilerThread0" #9 daemon prio=9 os_prio=2 tid=0x00000000234eb800 nid=0x2ae0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "JDWP Command Reader" #8 daemon prio=10 os_prio=0 tid=0x0000000023464800 nid=0xc50 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "JDWP Event Helper Thread" #7 daemon prio=10 os_prio=0 tid=0x000000002345f800 nid=0x1b0c runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "JDWP Transport Listener: dt_socket" #6 daemon prio=10 os_prio=0 tid=0x0000000023451000 nid=0x2028 runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000002343f800 nid=0x1ea0 waiting on condition [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x00000000233eb800 nid=0x10dc runnable [0x0000000000000000] java.lang.Thread.State: RUNNABLE "Finalizer" #3 daemon prio=8 os_prio=1 tid=0x00000000233d3000 nid=0xafc in Object.wait() [0x000000002472f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000741008e98> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143) - locked <0x0000000741008e98> (a java.lang.ref.ReferenceQueue$Lock) at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164) at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:209) "Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000021d0d000 nid=0x28ec in Object.wait() [0x000000002462f000] java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x0000000741006b40> (a java.lang.ref.Reference$Lock) at java.lang.Object.wait(Object.java:502) at java.lang.ref.Reference.tryHandlePending(Reference.java:191) - locked <0x0000000741006b40> (a java.lang.ref.Reference$Lock) at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153) JNI global references: 2504 Found one Java-level deadlock: ============================= "b": waiting to lock monitor 0x0000000021d10b58 (object 0x0000000741404050, a java.lang.Object), which is held by "a" "a": waiting to lock monitor 0x0000000021d13498 (object 0x0000000741404060, a java.lang.Object), which is held by "b" Java stack information for the threads listed above: =================================================== "b": at com.xiaozhi.juc.DeadLockDemo.lambda$main$1(DeadLockDemo.java:31) - waiting to lock <0x0000000741404050> (a java.lang.Object) - locked <0x0000000741404060> (a java.lang.Object) at com.xiaozhi.juc.DeadLockDemo$$Lambda$2/2101440631.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) "a": at com.xiaozhi.juc.DeadLockDemo.lambda$main$0(DeadLockDemo.java:20) - waiting to lock <0x0000000741404060> (a java.lang.Object) - locked <0x0000000741404050> (a java.lang.Object) at com.xiaozhi.juc.DeadLockDemo$$Lambda$1/1537358694.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) Found 1 deadlock.
As you can see, we found a deadlock through jstack 6276: "b" and "a"
-
Deadlock elimination scheme 2: jconsole
jconsole is a built-in analysis tool for java:
Spin lock
Spin lock means that the thread trying to obtain the lock will not block immediately, but will try to obtain the lock in a circular way.
- When the thread finds that the lock is occupied, it will continuously cycle to judge the state of the lock until it is obtained. This has the advantage of reducing the consumption of thread context switching, but the disadvantage is that the loop will consume CPU
//Spin lock public class AtomicReferenceThreadDemo { static AtomicReference<Thread>atomicReference=new AtomicReference<>(); static Thread thread; public static void lock(){ thread=Thread.currentThread(); System.out.println(Thread.currentThread().getName()+"\t"+"coming....."); while(!atomicReference.compareAndSet(null,thread)){ } } public static void unlock(){ System.out.println(Thread.currentThread().getName()+"\t"+"over....."); atomicReference.compareAndSet(thread,null); } public static void main(String[] args) { new Thread(()->{ AtomicReferenceThreadDemo.lock(); try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {e.printStackTrace();} AtomicReferenceThreadDemo.unlock(); },"A").start(); new Thread(()->{ AtomicReferenceThreadDemo.lock(); AtomicReferenceThreadDemo.unlock(); },"B").start(); } }