java juc two

16800199:

JMM

Please share your understanding of Volatile

Volatile is a lightweight synchronization mechanism provided by jvm (similar to synchronized, but not as powerful as synchronized)

  1. Ensure visibility
  2. No guarantee of atomicity
  3. Disable command rearrangement

What is JMM

JMM: Java memory model, something that does not exist, a concept! Promise!

JMM is JAVA memory model (java memory model). Because there are certain differences in memory access logic under different hardware manufacturers and different operating systems, the result is that when your code runs well and is thread-safe in a certain system environment, but changes to another system, various problems will occur. kind of problem. The Java memory model is to shield the differences between systems and hardware, so that a set of code can achieve the same access results under different platforms. JMM has matured and improved since the release of JSR-133 starting from Java 5.

Some synchronization conventions about JMM :

  1. Before the thread is unlocked, the shared variables must be 立刻flushed back to main memory.
    There is a shared variable in the main memory. Assume that thread a wants to operate the shared variable in the main memory. It will not directly operate the shared variable in the main memory. Instead, it will copy a copy to the working memory of thread a. If thread a modifies If the shared variable in its own thread memory is unlocked, the value of the updated shared variable needs to be assigned to the shared variable in the main memory before unlocking.
  2. Before a thread locks, the latest value in main memory must be moved to working memory.
  3. Locking and unlocking are the same lock.

Threads, working memory, main memory

Insert image description here

  1. Read the variable from the main memory and load it into the thread's working memory. The variable is loaded into the thread's working memory.

  2. Execution engine Use variable and assign (return).

  3. Write and store variables in working memory to main memory.

  4. lock and unlock

Insert image description here
This means that thread a executes slowly and the variables have not been refreshed to the main memory in time. Thread b has changed the variables and refreshed them to the main memory. At this time, thread a still holds the old variables, which causes a problem.

Thread a needs to know that the value in main memory has changed.

There are 8 types of memory interaction operations. The virtual machine implementation must ensure that each operation is atomic and indivisible (for double and long type variables, load, store, read and write operations allow exceptions on some platforms. )

  • lock: Acts on variables in main memory, marking a variable as thread-exclusive.
  • unlock: A variable that acts on main memory. It releases a variable that is in a locked state so that the released variable can be locked by other threads.
  • read (read): Acts on main memory variables. It transfers the value of a variable from main memory to the working memory of the thread for use in subsequent load actions.
  • load: A variable that acts on working memory. It puts the read operation from the main memory variable into the working memory.
  • use (use): Acts on 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, this instruction will be used.
  • assign (assignment): Acts on a variable in the working memory. It puts a value received from the execution engine into a copy of the variable in the working memory.
  • store (storage): Acts on variables in main memory. It transfers the value of a variable from working memory to main memory for subsequent write use.
  • write: Acts on variables in main memory. It puts the value of the variable obtained from the working memory by the store operation into the variable in main memory.

JMM has formulated the following rules for the use of these eight instructions:

  • One of the read and load, store and write operations is not allowed to appear alone. Even if you use read, you must load, and if you use store, you must write.

  • The thread is not allowed to discard its latest assign operation, that is, after the data of the working variable has changed, the main memory must be notified

  • A thread is not allowed to synchronize unassigned data from working memory back to main memory

  • A new variable must be created in main memory, and working memory is not allowed to directly use an uninitialized variable. That is, before performing use and store operations on variables, they must undergo assign and load operations.

  • Only one thread can lock a variable at the same time. After locking multiple times, you must perform the same number of unlocks to unlock.

  • If you perform a lock operation on a variable, the value of this variable in all working memory will be cleared. Before the execution engine uses this variable, you must reload or assign the value to initialize the variable.

  • If a variable is not locked, it cannot be unlocked. Nor can you unlock a variable that is locked by another thread.

  • Before unlocking a variable, the variable must be synchronized back to the main memory.

Volatile

程序不知道主内存的值已经被修改过了problem solved .

Ensure visibility

package com.atlinxi.gulimall.springdemo.juc.tvolatile;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    
    


    // 不加 volatile 程序就会死循环
    // 加 volatile 可以保证可见性
    private volatile static int num = 0;


    public static void main(String[] args) throws InterruptedException {
    
     // main线程

        /**
         * 线程1无限循环,此时main线程更改了num的值,那么线程1应该停止循环,因为他们操作的是同一变量,
         *      然而实际情况是,并没有
         */
        new Thread(()->{
    
     // 线程1 对主内存的变化是不知道的
            while (num==0){
    
    

            }
        }).start();

        TimeUnit.SECONDS.sleep(1);

        num = 1;

        System.out.println(num);
    }
}

No guarantee of atomicity

Atomicity: indivisible

When thread a is executing a task, it cannot be interrupted or divided. Either succeed at the same time or fail at the same time.

package com.atlinxi.gulimall.springdemo.juc.tvolatile;


//  volatile 不保证原子性
// 还是会两个线程同时占用,其实还是线程安全的问题
public class VDemo02 {
    
    

    private volatile static int num = 0;

    // 不加synchronized和volatile,每次的num值都不固定
    // 加synchronized是可以解决问题的
    public static void add(){
    
    
        num++;
    }

    public static void main(String[] args) {
    
    
        // 理论上num结果应该为2w
        for (int i = 0; i < 20; i++) {
    
    
            new Thread(()->{
    
    
                for (int j = 0; j < 1000; j++) {
    
    
                    add();
                }
            }).start();
        }


        while (Thread.activeCount()>2){
    
     // java 中有两个程序是默认在执行的,main gc
            Thread.yield();
        }


        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

How to ensure atomicity if lock and synchronized are not added?

Insert image description here
num++ in java is actually three steps in memory,

  1. getstatic gets this value
  2. iadd performs +1 operation
  3. putstatic writes back this value

Therefore, the bottom layer is not a step and its atomic operation cannot be guaranteed.

Use atomic classes to solve atomicity problems.

package com.atlinxi.gulimall.springdemo.juc.tvolatile;


import java.util.concurrent.atomic.AtomicInteger;

//  volatile 不保证原子性
// 还是会两个线程同时占用,其实还是线程安全的问题
public class VDemo02 {
    
    

//    private volatile static num = 0;
    // 使用源自类的Integer
    private volatile static AtomicInteger num = new AtomicInteger();

    // 不加synchronized和volatile,每次的num值都不固定
    // 加synchronized是可以解决问题的
    public static void add(){
    
    
        //num++; // num++ 本身就不是一个原子性操作
        num.getAndIncrement();  // AtomicInteger +1,底层不是简单的+1操作,用的是底层的 CAS,cpu的并发原理,效率比synchronized贼高
    }

    public static void main(String[] args) {
    
    
        // 理论上num结果应该为2w
        for (int i = 0; i < 20; i++) {
    
    
            new Thread(()->{
    
    
                for (int j = 0; j < 1000; j++) {
    
    
                    add();
                }
            }).start();
        }


        while (Thread.activeCount()>2){
    
     // java 中有两个程序是默认在执行的,main gc
            Thread.yield();
        }


        System.out.println(Thread.currentThread().getName() + " " + num);
    }
}

The bottom layer of atomic classes is directly linked to the operating system! Modify value in memory!
The Unsafe class is a very special existence!

Instruction rearrangement

The computer does not execute the program you write as you write it.

Source code-》Compiler optimization rearrangement--》Instruction parallelism may also be rearranged--》The memory system will also be rearranged--》Execution

处理器在执行指令重排的时候,它会考虑:数据之间的依赖性。

int x = 1;  // 1
int y = 2;  // 2
x = x + 5;  // 3
y = x * x;  // 4

// 我们认为和期望的执行顺序是1234,但是执行的时候可能会变成2134 1324
// 但不可能是4123
// 因为处理器在执行指令重排的时候,它会考虑:数据之间的依赖性

It may affect the results. Assume that the four values ​​​​of abxy are all 0 by default.

Thread a operates, x=a,b=1
Thread b operates, y=b,a=2

Normal result: x = 0, y = 0

Because x=a, b=1 has no dependency, the rearrangement of instructions may cause the order of execution to be reversed (the same goes for thread b). The
rearrangement of instructions leads to strange results, x=2, y=1;

指令重排是一个概念, it may not happen even if you write code ten million times, but in theory it will definitely happen.

As long as volatile is added, instruction rearrangement can be avoided! ! !

内存屏障There are CPU instructions in the system , whose functions are:

  1. Guarantee the execution order of specific operations!
  2. The memory visibility of certain variables can be guaranteed (visibility is achieved using these features volatile)!

Insert image description here
Volatile guarantees visibility. Atomicity cannot be guaranteed. Due to the memory barrier, instruction rearrangement can be avoided.

Singleton pattern

Hungry Chinese style

package com.demo.juc.single;


// 饿汉式单例
public class Hungry {
    
    


    // 饿汉式有可能会浪费内存

    // 这四组数据非常耗内存资源,饿汉式上来就把所有的资源全部加载进内存中了
    // 但是这四组数据的空间并没有被使用
    // 可能会浪费空间
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];


    // 私有构造器,别人就无法new这个对象了
    private Hungry() {
    
    
    }

    private final static Hungry hungry = new Hungry();

    public static Hungry getInstance(){
    
    
        return hungry;
    }
}

lazy man style

package com.demo.juc.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

// 懒汉式单例
public class LazyMan {
    
    

    private static boolean lx = false;

    private LazyMan() {
    
    

        synchronized (LazyMan.class){
    
    
            if (lx==false){
    
    
                lx = true;
            }else {
    
    
                throw new RuntimeException("不要试图使用反射破坏异常");
            }

            // 在使用反射和getInstance两种方式创建对象可以直接这么解决
//            if (lazyMan!=null){
    
    
//                throw new RuntimeException("不要试图使用反射破坏异常");
//            }
        }

        System.out.println(Thread.currentThread().getName() + "ok");
    }

    // 为了避免指令重排造成的现象,要让lazyMan避免指令重排
    private volatile static LazyMan lazyMan;

    // 双重检测锁模式的懒汉式单例,简称 DCL懒汉式
    public static LazyMan getInstance() {
    
    
        // 如果不上锁会出现线程安全的问题
        if (lazyMan==null){
    
    
            synchronized (LazyMan.class){
    
    
                if (lazyMan==null){
    
    
                    lazyMan = new LazyMan();  // 不是一个原子性操作
                    /**
                     * 1. 分配内存空间
                     * 2. 执行构造方法,初始化对象
                     * 3. 把这个对象指向内存空间
                     *
                     * 底层是三步操作,有可能会发生指令重排的现象
                     * 我们期望执行的步骤是123,真实有可能执行132
                     *
                     * 比如a线程执行132,先分配内存空间,再把空对象的内存空间占用了,占用之后再初始化对象
                     * 这个操作在cpu中是可以做到的
                     *
                     * 以上a线程是没有问题的,
                     * 但此时如果线程b进来,由于线程a将空对象指向内存空间了,线程b判断lazyMan是不等于null的,就会直接返回
                     * 然而此时lazyMan实际上还没有完成构造
                     *
                     *
                     * 这就是由于指令重排可能导致的现象
                     *
                     */
                }
            }
        }

        return lazyMan;
    }





    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
    
    
        // 多线程并发
//        for (int i = 0; i < 10; i++) {
    
    
//            new Thread(()->{
    
    
//                LazyMan.getInstance();
//            }).start();
//        }


        // 反射
        // 只要有反射,任何代码都不安全,任何私有都是纸老虎
        // LazyMan lazyMan1 = LazyMan.getInstance();

        // 获取空参构造器
        Constructor<LazyMan> declaredConstructors = LazyMan.class.getDeclaredConstructor(null);
        // 暴力反射,设置为可以直接调用私有修饰的成员方法
        // 无视了私有的构造器
        declaredConstructors.setAccessible(true);

        LazyMan lazyMan2 = declaredConstructors.newInstance();
        LazyMan lazyMan3 = declaredConstructors.newInstance();

        // com.demo.juc.single.LazyMan@14ae5a5
        //com.demo.juc.single.LazyMan@7f31245a
        System.out.println(lazyMan3);
        System.out.println(lazyMan2);


        /**
         * 以上得出,反射是可以破坏单例的
         *
         * 1. LazyMan.getInstance() 和 通过反射创建对象,
         *      这两种方式可以通过在空参构造再次判断该对象是否存在来解决
         *
         * 2. LazyMan lazyMan2 = declaredConstructors.newInstance();
         *    LazyMan lazyMan3 = declaredConstructors.newInstance();
         *
         * 如果两次对象都是通过反射来创建对象,那么我们的变量 static lazyMan就失效了,
         *      因为我们判断是否是单例都是判断它,而此时这种创建对象的方式直接跳过它了,
         *      它永远为null,我们就可以通过反射来一直创建对象
         *
         * 解决方式可以通过一个开关来决定
         *
         * 3. 就算这样,依然不是安全的,假设我们知道开关的变量名的话,
         *      还可以通过反射来获取变量,重置开关,单例模式就又失效了
         *
         * 道高一尺,魔高一丈
         */

    }
}

static inner class

package com.demo.juc.single;

// 静态内部类
public class Holder {
    
    

    private Holder() {
    
    
    }


    public static Holder getInstance(){
    
    
        return InnerClass.holder;
    }

    public static class InnerClass {
    
    
        private static final Holder holder = new Holder();
    }
}

enumerate

Insert image description here
If the type is an enumeration, you cannot use reflection to destroy the enumeration.
Enumeration is available in jdk1.5 and comes with singleton mode.

package com.demo.juc.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

// enum 是一个什么?本身也是一个Class类
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;
//        EnumSingle instance2 = EnumSingle.INSTANCE;
//        // true
//        System.out.println(instance1==instance2);


        EnumSingle instance1 = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        System.out.println(instance1==instance2);


        /**
         * Exception in thread "main" java.lang.NoSuchMethodException: com.demo.juc.single.EnumSingle.<init>()
         * EnumSingle没有这样的方法,意思就是说EnumSingle没有空参构造
         *
         * 然而我们查看idea target包下的class文件,或者使用javap -p反编译class文件,显示都是有私有空参构造的
         *    可能是有什么原因,以上两者实际上都是错误的
         *
         *    如果使用更专业的jad工具反编译的话,jad -sjava EnumSingle.class,就可以看到并没有空参构造
         *    只有一个有参构造,参数是String、int类型,
         *    我们此时Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
         *
         *    就会报 不能通过反射创建枚举对象的错误,就得到了我们想要的答案,枚举确实是单例的
         *
         *
         */



    }

}

CAS

What is CAS?

Why do you need to learn this? Because big companies must delve into the bottom layer.

CAS (Compare and Swap): Compare the value in the current working memory with the value in the main memory. If this value is expected, then the operation is performed, otherwise it is not performed! If not, it keeps looping.

Advantages: Comes with atomicity

shortcoming:

  1. Since the bottom layer is a spin lock, the loop will take time.
  2. Only one shared variable can be guaranteed to be atomic at a time. (Actually, that’s enough)
  3. ABA problem
package com.demo.juc.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    
    



    // CAS,compareAndSet:比较并交换!
    // 原子类的底层是用了CAS
    public static void main(String[] args) {
    
    

        AtomicInteger atomicInteger = new AtomicInteger(20);

        // 期望、更新
        // public final boolean compareAndSet(int expect, int update) {
    
    
        // 如果我期望的值达到了,那么就更新,否则就不更新
        // 再直白点儿就是,如果是我们的期望值20就更新成21,如果不是20就不更新

        // CAS 是CPU的并发原语!
        System.out.println(atomicInteger.compareAndSet(20, 21));
        System.out.println(atomicInteger.get());


        System.out.println(atomicInteger.compareAndSet(20, 21));
        System.out.println(atomicInteger.get());

        /**
         * 
         * true
         * 21
         * false
         * 21
         * 
         * 布尔值代表是否被更新了
         */
        


        atomicInteger.getAndIncrement(); // ++
    }
}

Implementation principle of Unsafe class getAndIncrement()

public class AtomicInteger extends Number implements java.io.Serializable {
    
    
	// ++ 的底层实现
	atomicInteger.getAndIncrement(); 
	
	
	
	
	
	public final int getAndIncrement() {
    
    
	return unsafe.getAndAddInt(this, valueOffset, 1);
	}
	
	
	// java无法操作内存,但是java可以调用c++(native),c++可以操作内存
	// Unsafe 相当于java留了一个后门,可以通过这个类操作内存
	private static final Unsafe unsafe = Unsafe.getUnsafe();
	private static final long valueOffset;
	
	static {
    
    
		try {
    
    
			// 获取内存地址的偏移值
			valueOffset = unsafe.objectFieldOffset
				(AtomicInteger.class.getDeclaredField("value"));
		} catch (Exception ex) {
    
     throw new Error(ex); }
	}
	
	private volatile int value;





//public final int getAndIncrement() {
    
    
// 	return unsafe.getAndAddInt(this, valueOffset, 1);
	}
	
// 与以上参数对照,this代表当前对象,第二个参数是当前对象内存的值,第三个参数是1
public final class Unsafe {
    
    
	public final int getAndAddInt(Object var1, long var2, int var4) {
    
    
		int var5;
		do {
    
    
			// 获取当前对象内存中的值
			var5 = this.getIntVolatile(var1, var2);
		// cas 比较并交换,
		// 如果当前对象(var1)内存中的值(var2)还是var5
		// 那就让var5 + 1

		// 这是一个内存操作,效率极高
		// 同时这是一个自旋锁
		} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
	
		return var5;
	}

ABA problem

Insert image description here

package com.demo.juc.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {
    
    



    // CAS,compareAndSet:比较并交换!
    // 原子类的底层是用了CAS
    public static void main(String[] args) {
    
    

        AtomicInteger atomicInteger = new AtomicInteger(20);

        // 期望、更新
        // public final boolean compareAndSet(int expect, int update) {
    
    
        // 如果我期望的值达到了,那么就更新,否则就不更新
        // 再直白点儿就是,如果是我们的期望值20就更新成21,如果不是20就不更新

        // CAS 是CPU的并发原语!


        // =================捣乱的线程======================
        System.out.println(atomicInteger.compareAndSet(20, 21));
        System.out.println(atomicInteger.get());


        System.out.println(atomicInteger.compareAndSet(21, 20));
        System.out.println(atomicInteger.get());



        // =======================期望的线程=====================
        System.out.println(atomicInteger.compareAndSet(20, 66));
        System.out.println(atomicInteger.get());


        /**
         * true
         * 21
         * true
         * 20
         * true
         * 66
         *
         *
         * 以上想表达的意思是,虽然第三个线程拿到的数依然和我们原来的数是一样的,都是20
         *      但是这个20并不是最初的那个,而是被前面两个线程更改过的,只不过值碰巧是一样的
         *
         * 对于我们平时写的sql:乐观锁!
         */
    }
}

atomic quotes

Solve the ABA problem and introduce atomic references.

Atomic operations with version numbers have the same principle as optimistic locking.

package com.atlinxi.gulimall.springdemo.juc.cas;
import com.fasterxml.jackson.databind.deser.std.AtomicReferenceDeserializer;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo {
    
    



    // CAS,compareAndSet:比较并交换!
    // 原子类的底层是用了CAS
    public static void main(String[] args) {
    
    

        // AtomicInteger atomicInteger = new AtomicInteger(20);

        // 第二个参数相当于版本号,
        // AtomicStampedReference 如果泛型是包装类,注意对象的引用问题
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(20,1);


        new Thread(()->{
    
    
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>"+stamp);

            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }

            // 后面两个参数的意思是,期望的版本号和修改以后的版本号
            System.out.println(atomicStampedReference.compareAndSet(20, 22, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println("a2=>"+atomicStampedReference.getStamp());


            System.out.println(atomicStampedReference.compareAndSet(22, 20, atomicStampedReference.getStamp(),
                    atomicStampedReference.getStamp() + 1));
            System.out.println("a3=>"+atomicStampedReference.getStamp());


        },"a").start();


        new Thread(()->{
    
    
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>"+stamp);

            // 两个线程都睡眠,是想上面的stamp都获取到最开始的值,就是我们初始化设置的1
            // 这个线程睡眠的时间比那个长是因为,我们想的是执行完a线程,再执行b线程,测试才能有我们想要的效果

            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }


            System.out.println(atomicStampedReference.compareAndSet(20, 66, stamp, stamp + 1));

            System.out.println("b2=>"+atomicStampedReference.getStamp());
        },"b").start();


        /**
         *
         *
         *
         * b1=>1
         * a1=>1
         * true
         * a2=>2
         * true
         * a3=>3
         * false
         * b2=>3
         *
         */
    }
}

Understanding of various locks

The following are some lock nouns. These classifications do not all refer to the status of the lock. Some refer to the characteristics of the lock, and some refer to the design of the lock. The content summarized below is a certain explanation of each lock noun.

Optimistic lock/pessimistic lock

pessimistic lock

Pessimistic locking is to assume the worst case scenario every time. Every time you go to get the data, you think that others will modify it, so you will lock it every time you get the data. In this way, if others want to get the data, they will be blocked until they get the lock. (Shared resources are only used by one thread at a time, other threads are blocked, and the resources are transferred to other threads after use).

Many such lock mechanisms are used in traditional relational databases, such as row locks, table locks, read locks, write locks, etc., which are all locked before operations.

Exclusive locks such as synchronized and reentrantLock in Java are the implementation of pessimistic locking ideas.

optimistic locking

Optimistic locking assumes the best situation. Every time you get the data, you think that others will not modify it, so it will not lock. However, when updating, it will judge whether others have updated the data during the next period. You can 版本号机制和CAS算法use The type of application that optimistic locking is suitable for 多读can improve throughput (that is, conflicts rarely occur, which can save overhead and increase system throughput). The write_condition mechanism in the database actually provides optimistic locking.

In Java, the atomic variable class under the Java.util.concurrent.atomic package is implemented using CAS, an implementation method of optimistic locking.

Exclusive lock/shared lock

An exclusive lock means that the lock can only be held by one thread at a time.

A shared lock means that the lock can be held by multiple threads.

Reentrant lock

Reentrant locks, also known as recursive locks, mean that when the same thread acquires the lock in the outer method, it will automatically acquire the lock when entering the inner method.

synchronized void setA() throws Exception{
    
    
  Thread.sleep(1000);
  setB();
}

synchronized void setB() throws Exception{
    
    
  Thread.sleep(1000);
}
package com.atlinxi.gulimall.springdemo.juc.lock;

public class Demo1 {
    
    

    public static void main(String[] args) {
    
    

        Phone phone = new Phone();

        new Thread(()->{
    
    
            phone.sms();
        },"a").start();

        new Thread(()->{
    
    
            phone.sms();
        },"b").start();


        /**
         *
         * asms
         * acall
         * bsms
         * bcall
         *
         * 以上结果证明是可重入锁,
         * 如果不是可重入锁的话,执行完sms之后锁就应该被释放了,输出的结果就不能保证顺序了
         */

    }
}


class Phone{
    
    
    public synchronized void sms(){
    
    
        System.out.println(Thread.currentThread().getName() + "sms");
        call(); // call方法中也有一把锁
    }

    public synchronized void call(){
    
    
        System.out.println(Thread.currentThread().getName() + "call");
    }
}













package com.atlinxi.gulimall.springdemo.juc.lock;

import java.util.concurrent.locks.ReentrantLock;

public class Demo2 {
    
    
    public static void main(String[] args) {
    
    

        Phone2 phone2 = new Phone2();

        new Thread(()->{
    
    
            phone2.sms();
        },"a").start();

        new Thread(()->{
    
    
            phone2.sms();
        },"b").start();


        /**
         *
         * asms
         * acall
         * bsms
         * bcall
         *
         * 以上结果证明是可重入锁,
         * 如果不是可重入锁的话,执行完sms之后锁就应该被释放了,输出的结果就不能保证顺序了
         */

    }
}



class Phone2{
    
    

    ReentrantLock lock = new ReentrantLock();

    public void sms(){
    
    

        try {
    
    
            lock.lock();  // 细节问题,synchronized可以理解为一把锁
                          // 而lock我们看到是两把锁,锁住又开这样
                          // lock锁必须配对,否则就会死在里面(产生死锁)
            System.out.println(Thread.currentThread().getName() + "sms");
            call(); // call方法中也有一把锁
        }finally {
    
    
            lock.unlock();
        }

    }

    public void call(){
    
    

        try {
    
    
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "call");
        }finally {
    
    
            lock.unlock();
        }
    }
}

Fair lock/unfair lock

Fair lock means that multiple threads acquire locks in the order in which they apply for locks.

Unfair lock means that the order in which multiple threads acquire locks is not in the order in which they apply for locks. It is possible that the thread that applied later acquires the lock before the thread that applied first. It is possible that it will cause priority inversion or starvation.

Fair lock: very fair, you can first come, first
served. Unfair lock: very unfair, you can jump in line.

The meaning of unfair lock is that if the execution time of the first thread is 3 minutes and the second thread is 3 seconds, if executed in order, the second thread that can be executed quickly will be blocked, causing performance inversion.

spin lock

In Java, spin lock means that the thread trying to acquire the lock will not block immediately, but will use a loop to try to acquire the lock. The advantage of this is to reduce the consumption of thread context switching, but the disadvantage is that the loop will consume the CPU.

package com.atlinxi.gulimall.springdemo.juc.lock;

import java.util.concurrent.atomic.AtomicReference;

/**
 * 自旋锁
 * 
 * 自定义自旋锁
 */
public class SpinlockDemo {
    
    

    // int 默认 0
    // Thread 默认 null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void myLock(){
    
    
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==>mylock");

        while (!atomicReference.compareAndSet(null,thread)){
    
    

        }
    }



    // 解锁
    public void myUnLock(){
    
    
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName() + "==>myUnlock");
        atomicReference.compareAndSet(thread,null);

    }


    /**
     *
     * a==>mylock
     * b==>mylock
     * a==>myUnlock
     * b==>myUnlock
     */
}











package com.atlinxi.gulimall.springdemo.juc.lock;

import java.util.concurrent.TimeUnit;

public class TestSpinlock {
    
    

    public static void main(String[] args) {
    
    
        SpinlockDemo lock = new SpinlockDemo();

        // 底层使用的自旋锁CAS
//        lock.myLock();
//        lock.myUnLock();

        new Thread(()->{
    
    
            lock.myLock();

            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.myUnLock();
            }

        },"a").start();


        try {
    
    
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }


        new Thread(()->{
    
    

            lock.myLock();

            try {
    
    
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                lock.myUnLock();
            }

        },"b").start();
    }
}

read-write lock

As the name suggests, a Reader-Writer Lock is a lock divided into two parts: a read lock and a write lock. The read lock allows multiple threads to acquire it at the same time, because the read operation itself is thread-safe, while the write lock is mutual. Exclusion locks do not allow multiple threads to obtain write locks at the same time, and write operations and read operations are also mutually exclusive.

In summary, the characteristics of read-write locks are: reading and reading are not mutually exclusive, reading and writing are mutually exclusive, and writing and writing are mutually exclusive.

Advantage analysis

  • Improved program execution performance: Multiple read locks can be executed simultaneously. Compared with ordinary locks that must be queued for execution under any circumstances, read-write locks improve program execution performance.

  • Avoid reading temporary data: read locks and write locks are mutually exclusive queue execution, which ensures that the read operation will not read half-written temporary data.

Read-write locks are suitable for business scenarios that require more reading and less writing. At this time, read-write locks have the greatest advantage.

deadlock

Insert image description here
Deadlock test, how to eliminate deadlock.

package com.atlinxi.gulimall.springdemo.juc.lock;

import java.util.concurrent.TimeUnit;

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() + lockA);


            try {
    
    
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            synchronized (lockB){
    
    
                System.out.println(Thread.currentThread().getName() + lockB);
            }
        }
    }
}


/**
t1lockA
t2lockB

下面就是卡死的了
*/

Solve the problem

  1. Use jps -l to locate the process number
    Insert image description here
  2. Use jstack process ID to find the deadlock problem
    View stack information
    Insert image description here

Part of the content is reproduced from:
https://www.cnblogs.com/null-qige/p/9481900.html
https://www.cnblogs.com/hustzzl/p/9343797.html

The increasing number of depression and anxiety disorders among Huawei employees is very worrying. Is there any way to make employees face life positively, openly and decently? I thought about it again and again, but couldn't figure it out.

Ren Zhengfei: To spend a life full of difficulties happily

Guess you like

Origin blog.csdn.net/weixin_44431371/article/details/132325848