Understanding of locks in java

Simulated deadlock code:

public class LockLearn {
    
    
    public static void main(String[] args) {
    
    
           deadlock();
    }
    private static void deadlock()
    {
    
    
        Object lock1=new Object();
        Object lock2=new Object();
        //线程1 拥有 lock1 试图获取lock2
        new Thread(()->{
    
    
            synchronized (lock1){
    
    
                System.out.println("获取 lock1 成功");
                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (lock2){
    
    
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();
        //线程2 拥有 lock2 试图获取lock1
        new Thread(()->{
    
    
            synchronized (lock2){
    
    
                System.out.println("获取 lock2 成功");
                try {
    
    
                    TimeUnit.SECONDS.sleep(3);
                }catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                synchronized (lock1){
    
    
                    System.out.println(Thread.currentThread().getName());
                }
            }
        }).start();

    }

}

When thread 1 has lock1, it tries to own lock2, and thread 2 has lock2 and tries to own lock1, causing a deadlock.

Pessimistic lock

The data adopts a conservative strategy for the modification of the outside world

It thinks that threads can easily modify the data

Therefore, the locked state will be adopted during the entire data modification process

Know that a thread is used up, other threads can continue to use

Demo code:

public class LockLearn2 {
    
    
    public static void main(String[] args) {
    
    
        synchronized (LockLearn2.class)
        {
    
    
            System.out.println("这是一个悲观锁的演示");
        }
    }


}

Compile code

// class version 52.0 (52)
// access flags 0x21
public class com/example/tangtang/boot/launch/JVM/LockLearn2 {
    
    

  // compiled from: LockLearn2.java

  // access flags 0x1
  public <init>()V
   L0
    LINENUMBER 5 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    RETURN
   L1
    LOCALVARIABLE this Lcom/example/tangtang/boot/launch/JVM/LockLearn2; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x9
  public static main([Ljava/lang/String;)V
    // parameter  args
    TRYCATCHBLOCK L0 L1 L2 null
    TRYCATCHBLOCK L2 L3 L2 null
   L4
    LINENUMBER 7 L4
    LDC Lcom/example/tangtang/boot/launch/JVM/LockLearn2;.class
    DUP
    ASTORE 1
    MONITORENTER
   L0
    LINENUMBER 9 L0
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
    LDC "\u8fd9\u662f\u4e00\u4e2a\u60b2\u89c2\u9501\u7684\u6f14\u793a"
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
   L5
    LINENUMBER 10 L5
    ALOAD 1
    MONITOREXIT
   L1
    GOTO L6
   L2
   FRAME FULL [[Ljava/lang/String; java/lang/Object] [java/lang/Throwable]
    ASTORE 2
    ALOAD 1
    MONITOREXIT
   L3
    ALOAD 2
    ATHROW
   L6
    LINENUMBER 11 L6
   FRAME CHOP 1
    RETURN
   L7
    LOCALVARIABLE args [Ljava/lang/String; L4 L7 0
    MAXSTACK = 2
    MAXLOCALS = 3
}

MONITORENTER is locked

MONITOREXIT unlock, release resources

Optimistic lock

Under normal circumstances, there will be no conflicts when the data is modified

No locks before data access

The data will be tested only when the data is submitted for changes

Optimistic locking in java is mostly achieved by (CAS) compare and swap comparison and exchange operations. CAS is a multi-threaded synchronization atomic instruction. The CAS operation contains three important information, namely the memory location, the expected original value and the new value. . If the value of the memory location is equal to the expected original value, then the value of the location can be updated to the new value, otherwise no modification is made.

CAS may cause ABA problems. ABA question 0 refers to the thread getting the original expected value A, but when CAS is about to be carried out, other threads preempt the execution right, changing this value from A to B, and then other threads change this value from B to A. However, the value of A at this time is no longer the original value of A, but the original thread does not know this situation. When it performs CAS, it only compares The original value is expected to be modified, which causes the ABA problem.

Take a police drama as an example. If someone puts a box of 100W cash at home, he will take it to redeem someone in a few minutes. However, when he is not paying attention, a thief comes in and exchanges an empty box for it. After leaving the box full of money, when someone comes in and sees that the box is exactly the same, he will think that this is the original box and take it to redeem the person. There must be a problem in this situation because the box is already empty. Yes, this is the problem of ABA.

The common handling method of ABA is to add the version number and update the version number after each modification. Take the above example, if every time the box is moved, the position of the box will change, and this change position is equivalent to "version Number", when someone comes in and finds that the location of the box has changed, they know that someone has moved their hands and feet, and will abandon the original plan, thus solving the ABA problem.

The AtomicStampedReference class provided by JDK 1.5 can also solve the ABA problem. This class maintains a "version number" Stamp. Each time it compares not only the current value but also the version number, it solves the ABA problem.

Optimistic locking has an advantage. It is locked at the time of submission, so it will not cause deadlock.

Reentrant lock

Recursive lock-refers to the same thread, if the outer function owns the lock, the inner function can continue to acquire the lock

Both synchronized and ReentrantLock in Java are reentrant locks

Demonstration of reentrant lock code:

public class LockLearn3 {
    
    
    public static void main(String[] args) {
    
    
        reentrantA();
    }

    private synchronized static void reentrantA() {
    
    
        System.out.println(Thread.currentThread().getName()+"执行reentrantA");
        reentrantB();
    }

    private synchronized static void reentrantB() {
    
    
        System.out.println(Thread.currentThread().getName()+"执行reentrantB");
    }


}

It can be seen from the results that the execution threads of the reentrantA method and the reentrantB method are both "main". We call the reentrantA method, and its method is nested with reentrantB. If synchronized is not reentrant, the thread will be blocked all the time.

The realization principle of reentrant locks is to store a thread identifier inside the lock to determine which thread the current lock belongs to, and a counter is maintained inside the lock. When the lock is idle, the value of this counter is 0. When the thread is occupied and reentered, add 1 respectively. When the lock is released, the counter is reduced by 1, until it is reduced to 0, it means that the lock is idle.

Shared lock and pessimistic lock

An exclusive lock means that at most one thread can hold the lock at any time. For example, synchronized is an exclusive lock, while a ReadWriteLock read-write lock allows multiple threads to perform read operations at the same time. It is a shared lock.

Exclusive locks can be understood as pessimistic locks, and mutual exclusion locks must be added every time a resource is accessed, while shared locks can be understood as optimistic locks, which relax the locking conditions and allow multiple threads to access the resource at the same time.

Guess you like

Origin blog.csdn.net/tangshuai96/article/details/111314542