Concurrent programming JUC

Keywords: Java JUC

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 AThread B
x=ay=b
b=1a=2

Normal results: x = 0, y =0; However, the following execution sequence may occur due to instruction rearrangement

Thread AThread B
b=1a=2
x=ay=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

  1. 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);
        }
    }
    

  2. 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;
        }
    }
    
  3. 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 constructor

    We 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
            });
        }
    }
    
  4. 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

  1. Use jsp -l to locate the process number

  2. Use jstack process number to view stack information and find deadlock

Posted by sholah on Sun, 26 Sep 2021 23:16:42 -0700