02_Reentrant lock (recursive lock)+LockSupport+AQS source code analysis

topic description

reentrant lock

Reentrant lock (recursive lock)
  • ①. It means that after the outer function of the same thread acquires the lock, the inner method of entering the thread will automatically acquire the lock ( 前提,锁对象是同一个对象)
    similar to the door in the home, after entering, you can enter the toilet, kitchen, etc.
  • ②. ReentranLock (display lock) and synchronized (implicit lock) in Java are both reentrant locks. One advantage of reentrant locks is that deadlocks can be avoided to a certain extent
  • ③. 隐式锁:(that is, the lock used by the synchronized keyword) is a reentrant lock by default (synchronization block, synchronization method)
  • The principle is as follows:掌握
  1. Each lock object has a lock counter and a pointer to the thread holding the lock
  2. When monitorenter is executed, if the counter of the target lock object is zero, it means that it is not held by other threads, and the Java virtual machine sets the holding thread of the lock object as the current thread, and adds 1 to its counter, otherwise it needs to wait , until the holding thread releases the lock
  3. When monitorexit is executed, the Java virtual machine decrements the counter of the lock object by 1. A counter of zero means the lock has been released

The four words of reentrant lock are explained separately:

Can: Can, Re: Again, Enter: Enter, Lock: Synchronize lock. into what? Entering a synchronization domain, i.e. a block/method of synchronized code or code locked by an explicit lock

Summary: Multiple processes in one thread can acquire the same lock, and hold this synchronization lock to re-enter. You can acquire your own internal lock yourself.

Types of reentrant locks
  • Implicit lock The lock used by the synchronized keyword, the default is a reentrant lock, synchronized code blocks and synchronized methods, at the JVM level
    • The implementation mechanism of Synchronized re-entry, automatic transmission car
  • The display lock Lock also has a reentrant lock like ReentrantLock, and the car with manual transmission

① Synchronous code block:

//1.同步块
public class SychronizedDemo {
    
    
    Object object=new Object();

    public void sychronizedMethod(){
    
    
       new Thread(()->{
    
    
           synchronized (object){
    
    
               System.out.println(Thread.currentThread().getName()+"\t"+"外层....");
               synchronized (object){
    
    
                   System.out.println(Thread.currentThread().getName()+"\t"+"中层....");
                   synchronized (object){
    
    
                       System.out.println(Thread.currentThread().getName()+"\t"+"内层....");
                   }
               }
           }
       },"A").start();
    }
    public static void main(String[] args) {
    
    
        new SychronizedDemo().sychronizedMethod();
        /*
        输出结果:
            A	外层....
            A	中层....
            A	内层....
        * */
    }
}

② Synchronization method:

package com.interview.juc;

/**
 * Copyright (C), 2018-2020
 * FileName: ReenterLockDemo
 * Author:   kongfanyu
 * Date:     2021/1/25 14:20
 */
public class ReenterLockDemo {
    
    
    public synchronized void m1(){
    
    
        System.out.println("===外");
        m2();
    }
    public synchronized void m2(){
    
    
        System.out.println("===中");
        m3();
    }
    public synchronized void m3(){
    
    
        System.out.println("===内");
    }

    public static void main(String[] args) {
    
    
        new ReenterLockDemo().m1();
    }
}

Analyze synchronized implementation from the perspective of bytecode
  • javap -c xxx.class file decompilation
  • synchronized synchronization code block
    • javap -c xxx.class file decompilation
    • insert image description here
    • The extra one ensures that when an exception occurs, you can exit and release the lock
  • synchronized ordinary synchronization method
  • synchronized static synchronization method
Synchronized reentrant implementation mechanism

Each lock object has a lock counter and a pointer to the thread holding the lock.

When monitorenter is executed, if the counter of the target lock object is zero, it means that it is not held by other threads, and the Java virtual machine sets the holding thread of the lock object as the current thread, and adds 1 to its counter.

In the case that the counter of the target lock object is not zero, if the holding thread of the lock object is the current thread, the Java virtual machine can add 1 to the counter, otherwise it needs to wait until the holding thread releases the lock.

When executing monitorexit, the Java virtual machine needs to decrement the counter of the lock object by 1. The counter is zero to indicate that the lock has been released.

The answer is in Java's object header. In the object header, there is a piece of data called Mark Word. On a 64-bit machine, Mark Word is 8 bytes (64 bits), and there are 2 important fields in these 64 bits: the lock flag bit and the thread ID that occupies the lock. Because of different versions of the JVM implementation, the data structure of the object header will have various differences, which will not be further discussed here. Here I mainly want to explain the idea of ​​​​lock implementation, because the detailed implementation of ReentrantLock is also based on similar ideas later. Based on this basic idea, synchronized will also have optimization strategies such as bias and spin. ReentrantLock will also use these optimization strategies, and it will be developed in detail in combination with the code.

In the HotSpot virtual machine, the layout of objects stored in memory can be divided into three areas: object header (Header), instance data (Instance Data) and alignment padding (Padding). The figure below shows the data structure of an ordinary object instance and an array object instance:
insert image description here
the functions of several parts of the object:

1. The Mark Word in the object header is mainly used to indicate the thread lock status of the object, and can also be used to cooperate with GC and store the hashCode of the object;

2. Klass Word is a pointer to the Class information in the method area, which means that the object can always know which Class instance it is;

3. The length of the array also occupies 64 bits (8 bytes), which is optional and only exists when the object is an array object;

4. The object body is the main part used to save object properties and values, and the memory space occupied depends on the number and type of properties of the object;

5. The alignment word is to reduce the fragmentation space of the heap memory.

Principles of Java Object Structure and Lock Implementation and Detailed Explanation of MarkWord

Show lock:

package com.interview.juc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReenterLockDemo {
    
    
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
    
    
       new Thread( ()->{
    
    
           lock.lock();
           try {
    
    
               System.out.println("外层----");
               lock.lock();
               try
               {
    
    
                   System.out.println("内层======");
               } finally {
    
    
                   lock.unlock();
               }
           }finally {
    
    
               lock.unlock();
           }
       },"线程A" ).start();
    }
}

If the number of locks does not correspond to the number of lock releases, a waiting state will occur, causing other threads to fail to execute.

package com.interview.juc;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Copyright (C), 2018-2020
 * FileName: ReenterLockDemo
 * Author:   kongfanyu
 * Date:     2020/11/25 14:20
 */
public class ReenterLockDemo {
    
    
    static Lock lock = new ReentrantLock();
    public static void main(String[] args) {
    
    
       new Thread( ()->{
    
    
           lock.lock();
           try {
    
    
               System.out.println("外层----");
               lock.lock();
               try
               {
    
    
                   System.out.println("内层======");
               } finally {
    
    
                   //lock.unlock();//注释之后,线程B不能执行
               }
           }finally {
    
    
               lock.unlock();
           }
       },"线程A" ).start();
       //========================
        new Thread( () ->{
    
    
            lock.lock();
            try
            {
    
    
                System.out.println("线程B执行.......");
            } finally {
    
    
                lock.unlock();
            }

        },"线程B").start();
    }
}

Another case:

//2.同步代码块
class Phone{
    
    
    public synchronized void sendSms() throws Exception{
    
    
        System.out.println(Thread.currentThread().getName()+"\tsendSms");
        sendEmail();
    }
    public synchronized void sendEmail() throws Exception{
    
    
        System.out.println(Thread.currentThread().getName()+"\tsendEmail");
    }

}
/**
 * Description:
 *  可重入锁(也叫做递归锁)
 *  指的是同一线程外层函数获得锁后,内层递归函数任然能获取该锁的代码
 *  在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
 *  也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
 *  **/
public class ReenterLockDemo {
    
    
    /**
     * t1 sendSms
     * t1 sendEmail
     * t2 sendSms
     * t2 sendEmail
     * @param args
     */
    public static void main(String[] args) {
    
    
        Phone phone = new Phone();
        new Thread(()->{
    
    
            try {
    
    
                phone.sendSms();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"t1").start();
        new Thread(()->{
    
    
            try {
    
    
                phone.sendSms();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        },"t2").start();
    }
}

显示锁:(i.e. lock) There are also reentrant locks like ReentrantLock
(note: as many locks as there are unlocks, they are used in pairs; if there is one more or one less, other threads will be in a waiting state)

class Phone2{
    
    
   static ReentrantLock reentrantLock=new ReentrantLock();

    public static void sendSms(){
    
    
        reentrantLock.lock();
        /*
        //reentrantLock.lock();
        注意有多少个lock,就有多少个unlock,他们是配对使用的
        如果多了一个lock(),那么会出现线程B一直处于等待状态
        * */
        reentrantLock.lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName()+"\t"+"sendSms");
            sendEmails();
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            reentrantLock.unlock();
        }
    }

    private static void sendEmails() {
    
    
        reentrantLock.lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName()+"\t"+"sendEmails...");
        }catch (Exception e){
    
    
            e.printStackTrace();
        }finally {
    
    
            reentrantLock.unlock();
        }
    }
}
public class ReentrantLockDemo {
    
    
    public static void main(String[] args) {
    
    
        Phone2 phone2=new Phone2();
        new Thread(()->{
    
    phone2.sendSms();},"A").start();
        new Thread(()->{
    
    phone2.sendSms();},"B").start();
    }
}

LockSupport

What is Lock Support?
java.util.concurrent.locks
public class LockSupport extends Object
用于创建锁和其他同步类的基本线程阻塞原语。
这个类与每个使用它的线程相关联,一个许可证(在Semaphore类的意义上)。 如果许可证可用,则呼叫park将park返回,在此过程中消耗它; 否则可能会阻止。 致电unpark使许可证可用,如果尚不可用。 (与信号量不同,许可证不能累积,最多只有一个。)
方法park和unpark提供了阻止和解除阻塞线程的有效手段,该方法不会遇到导致不推荐使用的方法Thread.suspend和Thread.resume目的不能使用的问题:一个线程调用park和另一个线程之间的尝试unpark线程将保持活跃性,由于许可证。 另外,如果调用者的线程被中断, park将返回,并且支持超时版本。 park方法也可以在任何其他时间返回,因为“无理由”,因此一般必须在返回之前重新检查条件的循环中被调用。 在这个意义上, park作为一个“忙碌等待”的优化,不浪费时间旋转,但必须与unpark配对才能有效。

park的三种形式也支持blocker对象参数。 线程被阻止时记录此对象,以允许监视和诊断工具识别线程被阻止的原因。 (此类工具可以使用方法getBlocker(Thread)访问阻止程序 。)强烈鼓励使用这些形式而不是没有此参数的原始形式。 在锁实现中作为blocker提供的正常参数是this 。

这些方法被设计为用作创建更高级同步实用程序的工具,并且本身对于大多数并发控制应用程序本身并不有用。 park方法仅用于形式的构造:

   while (!canProceed()) {
    
     ... LockSupport.park(this); } 
其中既不canProceed也没有任何其他动作之前的呼叫park需要锁定或阻止。 因为只有一个许可证与每个线程相关联, park任何中介使用可能会干扰其预期效果。

Important: The functions of park() and unpark() in LockSupport are to block and unblock threads respectively. Similar to wait and notify methods.

Thread waiting wake-up mechanism (wait/notify)

insert image description here
3 ways to make threads wait and wake up

  • Method 1: Use the wait method in the Object class to let the thread wait, and use the notify method to wake up the thread
  • Method 2: Use the await method of Condition in the JUC package to make the thread wait, and use the signal method to wake up the thread
  • Method 3: The LockSupport class can block the current thread and wake up the specified blocked thread

Method 1: The wait and notify methods in the Object class implement thread waiting and wakeup

package com.interview01;

/**
 * Copyright (C), 2018-2020
 * FileName: Demo1
 * Author:   kongfanyu
 * Date:     2020/12/3 17:30
 */
public class Demo1 {
    
    
    static Object objectLock = new Object();
    public static void main(String[] args) {
    
    
        new Thread( () ->{
    
    
            synchronized (objectLock){
    
    
                System.out.println(Thread.currentThread().getName()+"开始执行");
                try {
    
    
                    objectLock.wait();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"被唤醒");
            }
        },"A").start();

        new Thread( () ->{
    
    
            synchronized (objectLock){
    
    
                objectLock.notify();
                System.out.println(Thread.currentThread().getName()+"发出通知..");
            }
        },"B").start();

    }
}

Existing problems:

①wait and notify cannot be separated from the synchronization code block or synchronization method, and must obtain the lock of the monitoring object;

② Put notify in front of the wait method, the program cannot be executed and cannot be woken up; you can add sleep(3) to thread A to let the wakeup thread execute first.

//让唤醒线程先执行,就出现问题了,A线程一直处于等待状态,无法唤醒
try {
    
     TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) {
    
     }

Summary: The wait and notify methods must be used in a synchronous block or method and appear in pairs. Wait first and then notify to be ok.

The await and signal methods in the Codition interface implement thread waiting and wakeup

package com.interview01;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * Copyright (C), 2018-2020
 * FileName: Demo1
 * Author:   kongfanyu
 * Date:     2020/12/3 17:30
 */
public class Demo1 {
    
    
    static Object objectLock = new Object();
    static Lock lock = new ReentrantLock();
    static Condition condition = lock.newCondition();
    public static void main(String[] args) {
    
    
        new Thread( () ->{
    
    
            lock.lock();
            try {
    
    
                System.out.println(Thread.currentThread().getName()+"开始执行...");
                try {
    
    
                    condition.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"结束执行...");
            }finally {
    
    
                lock.unlock();
            }
        },"A").start();

        new Thread( () ->{
    
    
            lock.lock();
            try {
    
    
                condition.signal();//唤醒
                System.out.println(Thread.currentThread().getName()+"开始唤醒...");
            }finally {
    
    
                lock.unlock();
            }
        },"B").start();
    }
}

Existing problems:

①It also needs the support of lock and unlock synchronization blocks

② Similarly, if you wake up first and then wait for the problem of continuous blocking

The traditional synchronized and Lock implement the constraints of waiting for wake-up notifications:

(A) The thread must first obtain and hold the lock, and must be in the lock block (synchronized or lock),

(B) The thread must wait and wake up before it can be woken up.

The LockSupport class focuses on park waiting and unpark wakeup

  • what is

    • The operation of blocking and waking up threads is realized by the park() and unpark(thread) methods

    • Official website explanation

      public class LockSupport extends Object
      用于创建锁和其他同步类的基本线程阻塞原语。
      LockSupport类使用了一种名为Permit(许可)的概念来做到阻塞和唤醒线程的功能,每个线程都有一个许可(permit),permit只有两个值10,默认是零。
      
      可以把许可看成是一种(0,1)信号量(Semaphore),但与Semaphore不同的是,许可的累加上限是1.
      ------    
      这个类与每个使用它的线程相关联,一个许可证(在Semaphore类的意义上)。 如果许可证可用,则呼叫park将park返回,在此过程中消耗它; 否则可能会阻止。 致电unpark使许可证可用,如果尚不可用。 (与信号量不同,许可证不能累积,最多只有一个。)
      
  • main method

    • API: View Documentation

    • Blocking: park() / park(Object blocker) blocks the current thread/blocks the incoming specific thread

      //调用LockSupport.park()时
      public static void park(){
              
              
          UNSAFE.park(false,0L);
      }
      

      The permit default is 0, so when the park() method is called at the beginning, the current thread will be blocked until another thread sets the permit of the current thread to 1, the park method will be awakened, and then the permit will be set to 0 again and return . An access control system similar to a parking lot.

    • Wake up: unpark(Thread thread) wakes up the specified thread in the blocked state

      //调用LockSupport.unpark(thread);
      public static void unpark(Thread thread){
              
              
          if(thread != null){
              
              
              UNSAFE.unpark(thread);
          }
      }
      

      After calling the unpark(thread) method, the permit of the thread will be set to 1 (note that calling the unpark method multiple times will not accumulate, and the permit value is still 1) and will automatically wake up the thread thread, that is, the previously blocked LockSupport.park method Will return immediately.

  • the code

    public static void main(String[] args) {
          
          
        Thread a = new Thread(() ->{
          
          
            //先让B线程执行,发出通知,A线程还是可以正常结束
            try {
          
           TimeUnit.SECONDS.sleep(3); } catch(Exception e){
          
          e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"开始执行....");
            LockSupport.park();//被阻塞...等待通知放行,它要通过需要有许可证
            System.out.println(Thread.currentThread().getName()+"结束执行....");
        },"A");
        a.start();
        //A线程先执行,阻塞,B线程3秒后执行,成功通知A线程。
        //try { TimeUnit.SECONDS.sleep(3); } catch (Exception e){ e.printStackTrace(); }
        Thread b = new Thread(() ->{
          
          
            LockSupport.unpark(a);
            System.out.println(Thread.currentThread().getName()+"发出通知....");
        },"B");
        b.start();
    }
    

①Normal + no lock block requirement

② Wake up first and then wait for LockSupport to still support

  • Highlights

    LockSupport is used to create basic thread blocking primitives for locks and other synchronization classes.

    LockSupport is a thread blocking tool class. All methods are static methods, which allow threads to block at any position, and there are corresponding wake-up methods after blocking. In the final analysis, the native code in Unsafe called by LockSupport.

    LockSupport provides park() and unpark() methods to realize the process of blocking threads and unblocking threads:

    LockSupport has a permit associated with each thread that uses it. permit is equivalent to a switch of 1 and 0, and the default is 0.

    Call unpark once to add 1, from 0 to 1,

    Calling park once will consume permit, that is, change 1 to 0, and park will return immediately.

    If calling park again will become blocked (because permit is zero, it will block here until permit becomes 1), then calling unpark will set permit to 1. Each thread has a related permit, and there is only one permit at most, and repeated calls to unpark will not accumulate certificates.

    image understanding

    Thread blocking needs to consume certificate permit, and there is only one certificate at most.

    When the park method is called

    • If there is a certificate, it will directly consume the certificate and then exit normally;
    • If there is no certificate, it must block and wait for the certificate to be available;

    But unpark is the opposite, it will add a voucher, but there can only be one voucher at most, and the accumulation is invalid.

  • interview questions

    Why can the thread be blocked first after waking up the thread?

    Because unpark obtains a voucher, and then calls the park method, the voucher can be consumed justifiably, so it will not be blocked.

    Why does it block twice after waking up twice, but the end result also blocks the thread?

    Because the number of vouchers is at most 1, calling unpark twice in a row has the same effect as calling unpark once, and only one voucher will be added; while calling park twice needs to consume two vouchers, which are not enough, so it cannot be released.

Guess you like

Origin blog.csdn.net/kongfanyu/article/details/112726432