16,JMM
Understanding of Volatile
Volatile is a lightweight synchronization mechanism provided by the Java virtual machine (similar to synchronized, but not as powerful as it)
1. Ensure visibility
2. Atomicity is not guaranteed
3. Prohibit instruction rearrangement
What is JMM
JMM: Java Memory Model, which does not exist, is a concept and convention!
Some synchronization conventions about JMM:
-
The shared variable must be flushed back to main memory immediately before the thread is unlocked
-
Before a thread is locked, it must read the latest value from the main memory to the working memory of the thread
-
Locking and unlocking are the same lock
Thread working memory, main memory
There are 8 kinds of memory interactive operations. The virtual machine implementation must ensure that each operation is atomic and cannot be separated (for variables of double and long types, exceptions are allowed for load, store, read and write operations on some platforms)
- Read: acts on the main memory variable, which transfers the value of a variable from the main memory to the working memory of the thread for subsequent load action;
- load: the variable that acts on the working memory. It puts the read operation from the main memory into the working memory;
- Use: it acts on the variables in the working memory. It transfers the variables in the working memory to the execution engine. Whenever the virtual machine encounters a value that needs to be used, it will use this instruction;
- assign: a variable that acts on the working memory. It puts a value received from the execution engine into the variable copy of the working memory;
- store: a variable that acts on the main memory. It transfers the value of a variable from the working memory to the main memory for subsequent write;
- write: a variable that acts on the main memory. It puts the value of the variable obtained from the working memory by the store operation into the variable of the main memory;
- lock: a variable that acts on the main memory and identifies a variable as a thread exclusive state;
- unlock: a variable that acts on the main memory. It releases a locked variable, and the released variable can be locked by other threads;
Problems:
Therefore, JMM provides corresponding regulations for these 8 operations:
- One of read and load, store and write operations is not allowed to appear alone. That is, read must be loaded and store must be written
- The thread is not allowed to discard its latest assign operation, that is, after the data of the work variable has changed, it must inform the main memory
- A thread is not allowed to synchronize data without assign from working memory back to main memory
- A new variable must be born in main memory. Working memory is not allowed to directly use an uninitialized variable. This means that the variables must be assign ed and load ed before the use and store operations
- Only one thread can lock a variable at a time. After multiple locks, you must perform the same number of unlocks to unlock
- If you lock a variable, the value of this variable in all working memory will be cleared. Before the execution engine uses this variable, you must re load or assign to initialize the value of the variable
- If a variable is not locked, it cannot be unlocked. You cannot unlock a variable that is locked by another thread
- Before unlock ing a variable, you must synchronize the variable back to main memory
Problem: the program does not know that the value of main memory has been modified, but the thread will not stop running all the time:
/** * @author cVzhanshi * @create 2021-09-24 15:15 */ public class JMMDemo { private static Integer number = 0; public static void main(String[] args) { new Thread(() -> { while (number == 0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number = 1; System.out.println(number); } }
17,Volatile
1. Ensure visibility
/** * @author cVzhanshi * @create 2021-09-24 15:15 */ public class JMMDemo { // If volatile is not added, the program will loop // Adding volatile ensures visibility private volatile static Integer number = 0; public static void main(String[] args) { new Thread(() -> { // Thread 1 is not aware of changes in main memory while (number == 0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number = 1; System.out.println(number); } }
2. Atomicity is not guaranteed
Atomicity: indivisible
Thread A cannot be disturbed or divided when executing tasks. It either succeeds or fails at the same time.
/** * @author cVzhanshi * @create 2021-09-24 15:30 */ //Testing does not guarantee atomicity public class VDemo02 { //volatile does not guarantee atomicity private volatile static int num = 0; public static void add(){ num++; } public static void main(String[] args) { //Theoretically, num is 20000 for(int i = 1;i<=20;i++){ new Thread(() -> { for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2){ // main gc defaults to two threads Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); } }
num + + bottom layer (non atomic operation):
How to ensure atomicity without synchronized and lock
-
Use atomic classes to solve atomic problems
/** * @author cVzhanshi * @create 2021-09-24 15:30 */ public class VDemo02 { //volatile does not guarantee atomicity, but atomicity can be guaranteed by using atomic classes private volatile static AtomicInteger num = new AtomicInteger(); public static void add(){ num.getAndIncrement(); // AtomicInteger + 1 method, CAS } public static void main(String[] args) { //Theoretically, num is 20000 for(int i = 1;i<=20;i++){ new Thread(() -> { for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2){ // main gc defaults to two threads Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); } }
The bottom layers of atomic classes are directly linked to the operating system! Modify the value in memory! The Unsafe class is a very special existence
3. Prohibit instruction rearrangement
What is instruction rearrangement?
The computer does not execute the programs we write as we write
Source code – > compiler optimization rearrangement – > instruction parallelism may also rearrange – > memory system may also rearrange – > execution
When the processor rearranges instructions, it will consider the dependence between data!
For example:
int x=1; //1 int y=2; //2 x=x+5; //3 y=x*x; //4 //The expected execution order is 1_ 2_ 3_ 4 the possible execution sequence will become 2134 1324 //Could it be 4123? Impossible, because the result has changed
Possible impact results: the default values of a, B, x, y are 0
Thread A | Thread B |
---|---|
x=a | y=b |
b=1 | a=2 |
Normal results: x = 0, y =0; However, the following execution sequence may occur due to instruction rearrangement
Thread A | Thread B |
---|---|
b=1 | a=2 |
x=a | y=b |
The result of instruction rearrangement may be: x=2; y=1
Principle Exploration
volatile can avoid instruction rearrangement. volatile will add a memory barrier, which can ensure the order of instructions in this barrier
Memory barrier: CPU instruction. effect:
1. Ensure the execution sequence of specific operations;
2. Memory visibility of some variables can be guaranteed (with these features, the visibility of volatile implementation can be guaranteed)
Summary
- volatile ensures visibility;
- Atomicity cannot be guaranteed
- Due to the memory barrier, instruction rearrangement can be avoided
The most commonly used memory barrier is singleton mode
18. Completely play with singleton mode
Hungry Han single case mode
/** * @author cVzhanshi * @create 2021-09-25 16:13 */ // Hungry Han style single case public class Hungry { //It may waste space private byte[] data1 = new byte[1024*1024]; private byte[] data2 = new byte[1024*1024]; private byte[] data3 = new byte[1024*1024]; // Constructor privatization private Hungry(){} private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
DCL lazy singleton mode
-
Initial version
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // Privatization constructor private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } private static LazyMan lazyMan; public static LazyMan getInstance(){ if(lazyMan == null){ lazyMan = new LazyMan(); } return lazyMan; } public static void main(String[] args) { // It is absolutely correct and safe under single thread, but it is not safe under multi thread for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
If multithreading is unsafe, there is not only one object
-
Lock into DCL lazy singleton mode
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // Privatization constructor private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } private static LazyMan lazyMan; // Lazy singleton of double detection lock mode -- > DCL lazy public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); } } } return lazyMan; } public static void main(String[] args) { // It is absolutely correct and safe under single thread, but it is not safe under multi thread for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
The running result realizes the singleton, but it is not absolutely safe because lazyMan = new LazyMan(); Not an atomic operation
-
Final DCL singleton mode
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // Privatization constructor private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } // +volatile prevents instruction rearrangement private volatile static LazyMan lazyMan; // Lazy singleton of double detection lock mode -- > DCL lazy public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); // Not an atomic operation } } } return lazyMan; } public static void main(String[] args) { // It is absolutely correct and safe under single thread, but it is not safe under multi thread for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
Analysis: lazyMan = new LazyMan() is not an atomic operation
lazyMan = new LazyMan(); Steps to perform
1. Allocate memory space
2. Execute the construction method to initialize the object
3. Point this object to this space
The execution order may be changed to 1-3-2 due to instruction rearrangement
Result: thread A has not initialized the object, and thread B obtains the object lazyMan= Null returns the object. At this time, lazyMan has not completed the construction
Static inner class
//Static inner class public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.holder; } public static class InnerClass{ private static final Holder holder = new Holder(); } }
Single case unsafe (because of reflection)
-
Case 1: the first object is obtained through the class, the second object is created through the constructor through reflection, and the single instance is destroyed
- Code example:
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // Privatization constructor private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } // +volatile prevents instruction rearrangement private volatile static LazyMan lazyMan; // Lazy singleton of double detection lock mode -- > DCL lazy public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); // Not an atomic operation } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // It is absolutely correct and safe under single thread, but it is not safe under multi thread LazyMan lazyMan = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
- Solution: you can add judgment in the constructor
... private LazyMan(){ if(lazyMan != null){ throw new RuntimeException("Do not attempt to destroy a singleton by reflection"); } System.out.println(Thread.currentThread().getName() + "ok"); } ...//The omitted code is the same as above
-
Case 2: both objects are obtained by reflection
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // Privatization constructor private LazyMan(){ if(lazyMan != null){ throw new RuntimeException("Do not attempt to destroy a singleton by reflection"); } System.out.println(Thread.currentThread().getName() + "ok"); } // +volatile prevents instruction rearrangement private volatile static LazyMan lazyMan; // Lazy singleton of double detection lock mode -- > DCL lazy public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); // Not an atomic operation } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // It is absolutely correct and safe under single thread, but it is not safe under multi thread Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
Reason: objects are obtained through reflection, so the LazyMan in the original class is not constructed and is always null, so it can be judged by the constructor
Solution: set a traffic light (a sign, not the current object) to judge
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { private static boolean cvzhanshi = false; // Privatization constructor private LazyMan(){ synchronized (LazyMan.class){ if(cvzhanshi == false){ cvzhanshi = true; }else{ throw new RuntimeException("Do not attempt to destroy a singleton by reflection"); } } System.out.println(Thread.currentThread().getName() + "ok"); } // +volatile prevents instruction rearrangement private volatile static LazyMan lazyMan; // Lazy singleton of double detection lock mode -- > DCL lazy public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); // Not an atomic operation /** * lazyMan = new LazyMan();Steps to perform * 1,Allocate memory space * 2,Execute the construction method to initialize the object * 3,Point this object to this space * The execution order may be changed to 1-3-2 due to instruction rearrangement * Result: thread A has not initialized the object, and thread B obtains the object lazyMan= Null returns the object. At this time, lazyMan has not completed the construction */ } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // It is absolutely correct and safe under single thread, but it is not safe under multi thread Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
-
Case 3: on the basis of case 2, the "traffic light" was cracked and modified through reflection, thus destroying the single case
... public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // It is absolutely correct and safe under single thread, but it is not safe under multi thread Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // Get cvzhanshi property Field cvzhanshi = LazyMan.class.getDeclaredField("cvzhanshi"); cvzhanshi.setAccessible(false); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); cvzhanshi.set(lazyMan,false); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } ...//The omitted code is the same as above
Check the newInstance method and find that reflection cannot be used to destroy the singleton mode of enumeration
Try to break the singleton mode of enumeration class through reflection
-
Normally, the objects in the enumeration class are taken, which is indeed the singleton mode
/** * @author cVzhanshi * @create 2021-09-26 15:10 */ public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) { EnumSingle instance1 = EnumSingle.INSTANCE; EnumSingle instance2 = EnumSingle.INSTANCE; System.out.println(instance1); System.out.println(instance2); } }
-
By looking at the class file compiled by the enumeration class, you can see a parameterless constructor
package cn.cvzhanshi.single; public enum EnumSingle { INSTANCE; private EnumSingle() { } public EnumSingle getInstance() { return INSTANCE; } }
-
The constructor is called through reflection to construct the object and destroy the singleton
/** * @author cVzhanshi * @create 2021-09-26 15:10 */ public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
The result is unsatisfactory. There is no empty parameter constructor
Decompile the class file and check the code. It is found that there is also an empty parameter constructorWe use the more professional decompile tool jad.exe to check the source code. We can see that it is a parametric constructor
Conclusion: idea lied to us
public final class EnumSingle extends Enum { public static EnumSingle[] values() { return (EnumSingle[])$VALUES.clone(); } public static EnumSingle valueOf(String name) { return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name); } private EnumSingle(String s, int i) { super(s, i); } public EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }
-
After knowing the reason, continue to construct the object through reflection and constructor, and destroy the single case
.... class Test{ public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { EnumSingle instance1 = EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } } ...//The omitted code is the same as above
Through the results, we know that enumeration can not change the singleton mode through reflection
19. Deep understanding of CAS
What is CAS
CAS: compare the value in the current working memory with the value in the main memory. If the value is expected, perform the operation. If not, it will always cycle (spin lock)
/** * @author cVzhanshi * @create 2021-09-26 15:43 */ public class CASDemo { // CAS compareAndSet: compare and exchange public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2021); // expect and update // public final boolean compareAndSet(int expect, int update) // Method function: if the original data is the same as the expected value, it will be updated, otherwise it will not be updated. CAS is the concurrency primitive of CPU (computer level) System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); } }
Unsafe class
The Unsafe class performs the underlying + 1 operation
Summary:
CAS: compare the value in the current working memory with the value in the main memory. If the value is expected, perform the operation. If not, it will always cycle (spin lock)
Disadvantages:
- The cycle takes time
- One time can only guarantee the atomicity of one shared variable
- ABA problem
CAS: ABA problem (civet cat for Prince)
Illustration:
Although thread A has obtained the expected value, the expected value is not the initial value, but the value modified by B
/** * @author cVzhanshi * @create 2021-09-26 15:43 */ public class CASDemo { // CAS compareAndSet: compare and exchange public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2021); // expect and update // public final boolean compareAndSet(int expect, int update) // Method function: if the original data is the same as the expected value, it will be updated, otherwise it will not be updated. CAS is the concurrency primitive of CPU (computer level) // ===============Troublemaker thread=============== System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); // ===============Expected thread=============== System.out.println(atomicInteger.compareAndSet(2021, 6666)); System.out.println(atomicInteger.get()); } }
20. Atomic reference
Atomic operation with version number
To solve the ABA problem, the corresponding idea is to use the optimistic lock~
Code example:
/** * @author cVzhanshi * @create 2021-09-26 17:21 */ // Test atomic reference public class CASDemo02 { public static void main(String[] args) { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(2020, 1); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); //Get version number System.out.println("a1=>"+ stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(2020, 2022, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a2=>"+ atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(2022, 2020, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a3=>"+ atomicStampedReference.getStamp()); },"A").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); //Get version number System.out.println("b1=>"+ stamp); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(2020, 6666, stamp, stamp + 1)); System.out.println("b2=>"+ atomicStampedReference.getStamp()); },"B").start(); } }
Running result: it is wrong
Error reason: Integer uses the object caching mechanism. The default range is - 128 ~ 127. It is recommended to use the static factory method valueOf to obtain the object instance instead of new
**Use cache for valueOf, and new will create new objects and allocate new memory space** The number we use in the code is too large, so the atom does not refer to the same object, so the modification is unsuccessful.
Alibaba Development Manual:
Solution: reduce the number (i.e. do not use packaging classes as generics in production, and use User-defined objects such as User)
/** * @author cVzhanshi * @create 2021-09-26 17:21 */ // Test atomic reference public class CASDemo02 { public static void main(String[] args) { // AtomicStampedReference: if the generic is a wrapper class, you should pay attention to the reference of objects AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); // CAS compareAndSet: compare and exchange new Thread(() -> { int stamp = atomicStampedReference.getStamp(); //Get version number System.out.println("a1=>"+ stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a2=>"+ atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a3=>"+ atomicStampedReference.getStamp()); },"A").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); //Get version number System.out.println("b1=>"+ stamp); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(1, 6666, stamp, stamp + 1)); System.out.println("b2=>"+ atomicStampedReference.getStamp()); },"B").start(); } }
21. Understanding of various locks
21.1 fair lock and unfair lock
Fair lock: very fair. You can't jump the queue. You must come first
Unfair lock: very unfair. You can jump the queue (the default is unfair lock)
// Default unfair lock public ReentrantLock() { sync = new NonfairSync(); } // Change according to the passed in parameters public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
21.2 re entrant lock
Reentrant lock (recursive lock)
Synchronized version
/** * @author cVzhanshi * @create 2021-09-26 17:56 */ // Synchronized public class Demo01 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone{ public synchronized void sms(){ System.out.println(Thread.currentThread().getName()+"=> sms"); call();//There is also a lock here } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"=> call"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
lock version
/** * @author cVzhanshi * @create 2021-09-26 17:56 */ // Synchronized public class Demo02 { public static void main(String[] args) { Phone2 phone = new Phone2(); new Thread(()->{ phone.sms(); },"A").start(); new Thread(()->{ phone.sms(); },"B").start(); } } class Phone2{ Lock lock = new ReentrantLock(); public void sms(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+"=> sms"); call();//There is also a lock here } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName()+"=> call"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
Similarly, A needs to release both locks before B can get the lock
Summary
Details:
lock locks must be paired, or they will die inside
Differences between lock and synchronized versions:
- The lock version has two locks. When thread A gets the first one, it immediately gets the second one
- The Synchronized version has only one lock
21.3 spin lock
Custom locks using spin locks
/** * @author cVzhanshi * @create 2021-09-26 20:10 */ public class SpinlockDemo { // The generic type is Thread. The default value is null AtomicReference<Thread> atomicReference = new AtomicReference<>(); // Lock public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(Thread.currentThread().getName() + "==> myLock"); // Spin lock while (!atomicReference.compareAndSet(null,thread)){ } } // Unlock public void myUnLock(){ Thread thread=Thread.currentThread(); System.out.println(thread.getName()+"===> myUnlock"); atomicReference.compareAndSet(thread,null); } }
test
/** * @author cVzhanshi * @create 2021-09-26 20:19 */ public class TestSpinLock { public static void main(String[] args) { SpinlockDemo lock = new SpinlockDemo(); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { lock.myUnLock(); } },"T1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { lock.myUnLock(); } },"T2").start(); } }
Operation results:
T2 must wait until T1 releases the lock to get and release the lock. Spin and wait before that
21.4 deadlock
What is a deadlock
Deadlock test
/** * @author cVzhanshi * @create 2021-09-26 20:34 */ public class DeadLockDemo { public static void main(String[] args) { String lockA= "lockA"; String lockB= "lockB"; new Thread(new MyThread(lockA,lockB),"t1").start(); new Thread(new MyThread(lockB,lockA),"t2").start(); } } class MyThread implements Runnable{ private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA); } } } }
Deadlock troubleshooting and problem solving
-
Use jsp -l to locate the process number
-
Use jstack process number to view stack information and find deadlock