Interview questions about threads

1. To have multiple threads?

Existing programs are all working by one person (single thread)-the execution efficiency of the program is low. In order to improve the execution efficiency of the program, we can let multiple people to complete this task together is a multi-threaded program

1.2 What is a process? What is a thread?

process:

1) Process is the basic unit of resource allocation and management (the smallest unit of resource allocation) during the execution of concurrent execution programs.
2) A process can be understood as the execution process of an application. Once an application is executed, it is a process. Some programs depend on one process, and some programs can depend on many processes.
3) Each process has an independent address space. Every time a process is started, the system will allocate address space for it, establish a data table to maintain the code segment and stack Segment and data segment.

Thread:

It is the smallest unit that the operating system can execute, and the process includes threads.
 The operation of a process depends on at least one thread-----single-threaded program is inefficient and
  the operation of a process relies on multiple threads-----multi-threaded program is more efficient.
Another very appropriate example, most of us use Let's go through the deduction. When we open a deduction, we actually start a process, and then we send a paragraph of text, that is, a thread is opened, and we send a voice, that is, another thread is started, then in this deduction There are two threads for text and language in the process of .Of course, there may be other threads!

1.3 The difference between process and thread?

1) Address space: All threads of the same process share the address space of this process, and the address space between different processes is independent.
2) Resource ownership: All threads of the same process share the resources of this process, such as memory, CPU, IO, etc. The resources between processes are independent and shared in the same way.
3) Execution process: Each process can be said to be an executable application, and each independent process has an entry for program execution, which executes the sequence sequentially. However, threads cannot be executed independently, and must be controlled by the program's multithreaded control mechanism depending on the existence of the application program.
4) Robustness: Because all threads of the same process share the resources of this thread, when a thread crashes, the process also crashes. But the resources between each process are independent, so when one process crashes, it will not affect other processes. Therefore, processes are more robust than threads.
  Thread execution overhead is small, but it is not conducive to resource management and protection.
 The execution overhead of the process is small, but resource management and protection can be carried out. The process can move forward across machines.

1.4 What are the prerequisites for processes and threads?

Because the process is the basic unit of resource allocation, the thread is the smallest unit of program execution. And consider the robustness between the process and the thread.
1. In the program, if you need to create and destroy frequently, use threads. Because process creation and destruction are expensive (requiring constant resource allocation), but frequent thread calls just change the execution of the CPU, and the overhead is small.
2, If you need a more stable and safe program, you can choose the process. If you pursue speed, Just choose the thread.

2. How to create multiple threads

Generally speaking, there should be three ways to create multiple threads:

  1. Inherit the Thread class
  2. Implement Runnable interface
  3. Anonymous inner class

Before that, it is necessary to talk about this main, which is the main thread often seen in writing Java programs. The code expression is

public static void main(String[] args){
do…
}

This main is called the main thread, which is the entry point of the program, and is created by the JVM, which is the Java virtual machine.
Here are three ways to create threads . The
first is to inherit from the Thread class. Look at the code.

public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
	//3,设置线程名称
	t1.setName("灭霸");
	t2.setName("猪猪侠");
	//4,启动线程
	t1.start();
	t2.start();
 	 		}
	}
  class MyThread extends Thread{
	//1,把所有的业务,写在重写的run()里
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			//2,getName()线程名称
			System.out.println(getId()+super.getName()+"===="+i);
&emsp;&emsp;&emsp;			}	
	&emsp;	}
}

The second way to create a thread is to implement the Runnable interface


	public static void main(String[] args) {
	MyRunnable target = new MyRunnable();
	   Thread t1 = new Thread(target);
	   Thread t2 = new Thread(target);
	//3,设置线程名称
       t1.setName("灭霸");
	   t2.setName("猪猪侠");
	//4,启动线程
	t1.start();
	t2.start();
	}

}

class MyRunnable implements Runnable{
	//1,把所有的业务,写在重写的run()里
	@Override
	public void run() {
		for(int i=0;i<100;i++) {
			//2,Thread类里的静态currentThread()返回当前正在执行任务的线程对象的引用
			System.out.println(Thread.currentThread().getName()+"===="+i);
		}
		
	}
}

Next, let's look at the last way to create threads, through anonymous inner classes


	public static void main(String[] args) {
		System.out.println("主线程在执行...");
		//方法一:
		new Thread(){     //1,继承Thread类
			public void run() { //2,重写run方法
				for(int i=0;i<100;i++) {  //3,将要执行的代码写在run方法中
					System.out.println("匿名内部类创建线程1...");
				}			
			}
		}.start();
		//方法二:
		new Thread(new Runnable() {//1.将Runnable的子类对象传递给Thread的构造方法

			@Override
			public void run() {
				for(int i=0;i<100;i++) {
					System.out.println("匿名内部类创建线程2...");
				}			
			}			
		}).start();//开启线程
	}

}

It should be noted here that the anonymous inner class should be written in the method, here is written in the method,
Insert picture description here
that is to say, there is a run method in the thread object, why can't the execution thread directly call this run method? What about calling start to start the thread?
In fact, it is easy to understand, if the run method is called directly, then what is the difference between this and the usual class? You must know that the thread is an independent thread. If you call the run method directly, it is not equivalent to directly executing this method, just like an ordinary class and then calling one of its methods, but here is the thread.
Speaking of the official point, for threads, there is a concept of thread planner, which can be understood as a gadget that specifically manages thread execution. Only when you call start, will this thread be handed over to the thread planner to manage, only It is handed over to the thread planner to truly be regarded as a thread. Otherwise, it is an ordinary class, not a thread.
The three ways to create a thread are mentioned above. So, which one is better to use? Actually, it may be There may be more ways to implement the Runnable interface. Why?
It is also very simple, because in Java, classes can only be inherited by single, so if you use the Thread class, you can no longer inherit from other classes. This is in practice There are bound to be some limitations in the development, but the way to implement the Runnable interface can avoid this limitation, because the interface supports multiple implementations and can also inherit other classes. In this case, the flexibility is higher. a lot of.

A few knowledge points to know so far

What is a thread? What is a process?
The difference between a thread and a process?
Three ways to create a thread.
Why not call run
which way to create a thread is better? Why

3. Thread commonly used API

Insert picture description here
In this code, there is no difference from the thread creation code written before, that is, it is added when printing

Thread.currentThread().getName()

It is easy to understand, that is, to get the name of the current thread. Look at the output result.
Insert picture description here
Here is another method: isAlive()

This method is used to determine whether the current thread is active, so you first need to know what is "active"

The so-called active state is that the thread has started and has not stopped. According to this understanding, look at a piece of code and take a thread as an example

/**
 * 创建线程的第一种方式
 * 继承自Thread类
 */
class A extends Thread{
    @Override
    public void run() {
        System.out.println("正在执行线程A。。。。"+Thread.currentThread().getName());
        System.out.println("线程的活动状态是:"+Thread.currentThread().isAlive());

    }

}

Then execute

        A a = new A();
        System.out.println("此时线程的状态是:"+Thread.currentThread().isAlive());
        a.start();
        System.out.println("此时线程的状态是:"+Thread.currentThread().isAlive());

Look at the execution result again. Look at the execution result
Insert picture description here
against the code. It can be seen that for thread a, only when the start method is called, the thread starts executing, that is, it is active.

In addition, there is a more familiar method, sleep(), which is a method to temporarily sleep a thread. It should be noted here that the currently executing thread is suspended. Let’s look at a specific example.

  //执行线程A
        A a = new A();
        a.start();
        System.out.println(System.currentTimeMillis());
        try {
            Thread.sleep(2000);
            System.out.println(System.currentTimeMillis());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

The following code is added to the code to make the thread sleep for 2 seconds

hread.sleep(2000);

Then look at the printout results. The
Insert picture description here
thread pauses for 2000 milliseconds, which is 2 seconds, during the execution. This also has some special uses in normal development. You can write it out when you need it.
All of the above are introducing some common APIs of threads. In fact, there is another one that should be known, that is, getId(), which is the only indication of a live thread. For example, there are the following usages.
Insert picture description here
See
Insert picture description here
the thread ID obtained from the printout. It can be used as the only indicator to determine this thread

4. The stop of the thread

For a thread, it has such a life cycle, which is new, ready, running, blocking and dying

4.1 Thread state/thread life cycle
  New state: The thread that just came out is a new state.
  Ready state: waiting for CPU scheduling (even if the start method has been called at this time, the thread will not execute immediately, and must wait until jvm calls run The method thread is actually executed, and the current state is the ready state)
  Running state: officially starts to execute the task, that is, the
   blocking state after calling the run method : in the running state, if the sleep method is called, it will be in a blocked state and
  die state (ideal Status): That is, the thread is stopped

Focus on the death of the thread: calling the stop method can stop the thread, but nowadays, stop is no longer recommended, most stop a thread will use the Thread.interrupt() method.
As long as this method is called, the thread will stop. In fact, calling interrupt is only equivalent to marking the current thread with a stop mark. At this time, the thread is not really stopped, and it is obvious that some steps are missing

/**
 * 创建线程的我第一种方式
 * 继承自Thread类
 */
class A extends Thread{
    @Override
    public void run() {
        System.out.println("正在执行线程A。。。。"+Thread.currentThread().getName());
        for (int i=0;i<10;i++){
            System.out.println(i);

        }

    }

}

//执行线程A
        A a = new A();
        a.start();
        a.interrupt();

If you see the above code, do you think the thread will be stopped?
The actual answer is no. The above does not really stop the thread, but puts a stop mark. What to do, here needs to add a judgment.
There is the following code to execute the thread

 //执行线程A
      A a = new A();
      a.start();
      //此处打上一个停止的标记
      a.interrupt();

And interrupt has been called, which is equivalent to the thread has been marked with an interrupt mark, but the thread has not stopped at this time, and the following judgments need to be made. What does it mean to
insert a picture description here
Insert picture description here
? That is to say, the code after the for loop will still be executed. From this point of view, it seems that the thread has not really been stopped. Then, how to deal with this situation?

/**
 * 创建线程的我第一种方式
 * 继承自Thread类
 */
class A extends Thread{
    @Override
    public void run() {
        System.out.println("正在执行线程A。。。。"+Thread.currentThread().getName());
        try {
            for (int i=0;i<10;i++){
                if (this.isInterrupted()){
                    System.out.println("线程已经停止");
                    System.out.println("当前线程的状态:"+this.isInterrupted());
                    throw new InterruptedException();
                }
                System.out.println(i);
            }
            System.out.println("此处还会被执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The above is a processing method, we throw an interrupt exception after judging the interrupt flag, and then catch the exception, so as to avoid the situation that the alligator bureau continues to be executed after the for loop, that is, the thread is truly stopped.

Therefore, a good way to stop the thread is to throw the exception above.

5. Thread safety issues

So why is there a thread safety issue? To put it simply, thread safety problems will occur when multiple threads access a shared variable at the same time in time . Let’s take a simple example and look at the
ticket sales case.
Requirements: Let four windows sell 100 tickets together.

package cn.tedu.thread;
		//模拟多线程售票  -- 继承Thread
		public class Test4_Tickets {
			public static void main(String[] args) {
				//4,启动线程
				MyTickets t1 = new MyTickets();
				MyTickets t2 = new MyTickets();
				MyTickets t3 = new MyTickets();
				MyTickets t4 = new MyTickets();
				t1.start();
				t2.start();
				t3.start();
				t4.start();
				//--问题1:总共需要卖出100张票,但是,现在却卖出了400张票.为什么?
				//成员变量tickets,每次实例化时,都会跟着对象初始化一次.4个对象就各自拥有一个tickets,总共变成了400张
				//--问题1的解决方案:把共享资源tickets加static修饰,变成全局唯一全局共享
				//目前来看,程序中的4个线程完美的配合着卖了100张票.
				//但是,数据最终有没有安全隐患--让程序休眠一会儿
			}
		}
		//1,创建多线程类-extends Thread
		class MyTickets extends Thread{
			//--需求:让四个窗口,一起卖出100张票
			static int tickets = 100 ;//定义变量,记录票数
			//2,开始卖票--把业务放入run()
			@Override
			public void run() {
				//一直卖票,卖完结束
				while(true) {//死循环----配置设置好出口!!
					if(tickets>0) {
						//!!! 5,验证多线程中,数据是否安全,都要接受这次考验
						//问题2: 超卖: 卖出了0号票,甚至-1号票
						//问题3: 重卖: 同一张票卖给了好几个
						try {
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						//3,打印线程名称
						System.out.println(getName()+"===="+tickets--);
					}else {
						break;//合理的出口!!
					}
				}
			}
		}

operation result:

Insert picture description here
Summary of the problem:
-1, oversold: sold 0 -1 -2 tickets
-2 , resale: sold the same ticket to several people

问题分析
	--1,代码
		package cn.tedu.thread;
		//模拟多线程售票  -- 实现Runnable
		public class Test5_Tickets2 {
			public static void main(String[] args) {
				//4,测试
				MyTickets2 target = new MyTickets2();
				Thread t1 = new Thread(target);
				Thread t2 = new Thread(target);
				Thread t3 = new Thread(target);
				Thread t4 = new Thread(target);
				t1.start();
				t2.start();
				t3.start();
				t4.start();
			}
		}
		//1,创建多线程类--implements Runnable
		class MyTickets2 implements Runnable{
			//需求:让四个窗口,一起卖出100张票
			int tickets = 100 ;//定义变量,记录票数
			//2,开始卖票--把业务放入run()
			@Override
			public void run() {
				while(true) {//死循环----配置设置好出口!!
					if(tickets>0) {
						//让程序休眠一会儿,来检测多线程中数据安全隐患
						//问题1: 超卖: 卖出了0号票,甚至-1号票,-2号票
						//问题2: 重卖: 同一张票卖给了好几个人
						try {
				//当tickets=1时,满足了判断条件,t1 t2 t3 t4都准备卖票,一进来4个人就都睡了
							Thread.sleep(10);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						//3,打印线程名称--Thread.currentThread()
				//超卖的原因:
				//假设t1先醒,此时tickets=1,执行tickets--,输出1,把tickets自减变成0,刚变完
				//假设t2也醒了,此时tickets=0,	执行tickets--,输出0,把tickets自减变成-1,刚变完
				//假设t4也醒了,此时tickets=-1,执行tickets--,输出-1,把tickets自减变成-2,刚变完
				//假设t3也醒了,此时tickets=-2,执行tickets--,输出-2,把tickets自减变成-3		
				
				//重卖的原因:
				//假设t1先醒,此时	tickets=28,执行tickets--,输出28,来没来的及自减变成27呢...
				//假设t2也醒了,此时tickets=28,执行tickets--,输出28,来没来的及自减变成27呢...
				//假设t4也醒了,此时tickets=28,执行tickets--,输出28,把tickets自减变成27
				//假设t4也醒了,此时tickets=27,执行tickets--,输出27....		
				System.out.println(Thread.currentThread().getName()+"=="+tickets--);
					}else {
						break;//合理的出口!!
					}
				}
			}
		}

Solution-Synchronization Lock
What is a synchronization lock? A
synchronization lock is to wrap up the code that may have problems and let only one thread execute at a time. Synchronization is achieved through the sychronized keyword. When multiple objects operate on shared data, you can use synchronization locks to solve thread safety issues.
Insert picture description here
  Use the keyword synchronized to indicate synchronization. Multiple threads need to queue to access shared resources instead of preemption.
  -After synchronization, the advantage is: shared data is safe. The disadvantage is: low efficiency.-Sacrificing efficiency, Security is achieved.
   Synchronized can modify methods and code blocks.
-Synchronization method-The lock object used is this
  synchronized public void eat(){} -Synchronized
code block-The lock object can be
  synchronized (object) {    need to be synchronized Code;   }

--implements Runnable
			package cn.tedu.thread;
			//解决 线程安全隐患
			public class Test1_Lock {
			    public static void main(String[] args) {
			        //4,测试
			        MyTickets2 target = new MyTickets2();
			        Thread t1 = new Thread(target);
			        Thread t2 = new Thread(target);
			        Thread t3 = new Thread(target);
			        Thread t4 = new Thread(target);
			        t1.start();
			        t2.start();
			        t3.start();
			        t4.start();
			    }
			}
			class MyTickets2 implements Runnable{
			    //需求:让四个窗口,一起卖出100张票
			    int tickets = 100 ;
			    Object obj = new Object();
			    String s = "jack";
			    //目前程序中,由于在多线程编程中,出现了多个线程抢占资源而造成的数据错乱.
			    //加锁来解决数据安全隐患问题.考虑以下两个问题:
			    //1,锁的位置:把会有问题的代码锁起来,从问题源头开始,到结束为止,都包起来
			    //2,锁的对象:代码块里使用锁,需要考虑锁对象是谁?可以是任意对象,只要是同一个对象就行
			    //3,同步锁也可以直接锁方法,默认是用的锁对象是this
			    //4,如果共享资源是 一个 静态资源,锁对象必须是 类名.class!!!
			//  synchronized public void run() {
			    @Override
			    public void run() {
			        while (true) {
			//            synchronized (new Object()) {//还有问题,四个线程就产生了4个对象!!
			//            synchronized (obj) {//从头到尾都是只有一个对象!!!
			//            synchronized (s) {//从头到尾都是只有一个对象!!!
			            synchronized (this) {//从头到尾都是只有一个对象!!!
			                if (tickets > 0) {
			                    try {
			                        Thread.sleep(10);
			                    } catch (InterruptedException e) {
			                        e.printStackTrace();
			                    }
			                    //3,打印线程名称--Thread.currentThread()
			                    System.out.println(Thread.currentThread().getName() + "==" + tickets--);
			                } else {
			                    break;//合理的出口!!
			                }
			            }
			        }
			    }
			}
--extends Thread
			package cn.tedu.thread;
			//模拟多线程售票  -- 继承Thread
			public class Test4_Tickets {
				public static void main(String[] args) {
					//4,启动线程
					MyTickets t1 = new MyTickets();
					MyTickets t2 = new MyTickets();
					MyTickets t3 = new MyTickets();
					MyTickets t4 = new MyTickets();
					t1.start();
					t2.start();
					t3.start();
					t4.start();
					//--问题1:总共需要卖出100张票,但是,现在却卖出了400张票.为什么?
					//成员变量tickets,每次实例化时,都会跟着对象初始化一次.4个对象就各自拥有一个tickets,总共变成了400张
					//--问题1的解决方案:把共享资源tickets加static修饰,变成全局唯一全局共享
					//目前来看,程序中的4个线程完美的配合着卖了100张票.
					//但是,数据最终有没有安全隐患--让程序休眠一会儿
				}
			}
			//1,创建多线程类-extends Thread
			class MyTickets extends Thread{
				//--需求:让四个窗口,一起卖出100张票
				static int tickets = 100 ;//定义变量,记录票数
				//2,开始卖票--把业务放入run()
				@Override
				public void run() {
					//一直卖票,卖完结束
					while(true) {//死循环----配置设置好出口!!
						//如果共享资源是 一个 静态资源,锁对象必须是 类名.class!!!
						synchronized (MyTickets.class) {
							if(tickets>0) {
								//!!! 5,验证多线程中,数据是否安全,都要接受这次考验
								//问题2: 超卖: 卖出了0号票,甚至-1号票
								//问题3: 重卖: 同一张票卖给了好几个人
								try {
									Thread.sleep(10);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
								//3,打印线程名称
								System.out.println(getName()+"===="+tickets--);
							}else {
								break;//合理的出口!!
							}	
						}
					}
				}
			}

7, the communication between threads wait and notify

Threads are not independent individuals, and threads can communicate and cooperate with each other. Then about the communication between threads, the main thing to learn is to wait and notify, that is to say, in the communication between learning threads, the two methods that may be dealt with most are wait() and notify().
 In the communication between threads, wait means let this thread enter the waiting state, and the subsequent code will no longer be executed. There is a prerequisite here is that Java must implement the synchronization lock for each Object object Wait and notify are added, but they cannot be called casually, such as this

        String s = new String();
        try {
            s.wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

However, it is another matter to be able to call the normal call, and whether there is an error. For example, the above code calls the wait method, and then executes
it. Insert the picture description here. The
Insert picture description here
result is an error. Why? Because there is a prerequisite for using wait and notify, that is, the synchronization lock must have been obtained in advance. The following is the correct way to use


        try {
            String s = new String();
            synchronized (s){
                s.wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

The above execution will not report an error again, because the synchronization lock has been added. This is just to show that the premise for the wait and notify calls is that the synchronization lock must have been obtained.
 Maybe it is not very clear, then remember one sentence: wait and notify can only be called in synchronized code blocks and synchronized methods.

Let's continue to look at an example


    public static void main(String[] args) {
        Object lock = new Object();
        MyThread myThread = new MyThread(lock);
        Thread thread = new Thread(myThread);
        thread.start();
        thread.setName("线程A");

    }
}

class MyThread implements Runnable{
private Object lock;
public MyThread(Object lock){
    this.lock = lock;
}
    @Override
    public void run() {
        System.out.println("线程开始工作"+Thread.currentThread().getName());
        synchronized (lock){
            try {
                lock.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("线程结束工作"+Thread.currentThread().getName());
    }
}

First look at the custom MyThread thread. First, an object object is created as the lock object, and then the wait method is called in the synchronization code block. The purpose of calling this method is to make the thread in a waiting state. In this way, the following The code will no longer be executed. At the same time, the lock held by the thread is released when the wait method is called to put the thread in the waiting state.
Insert picture description here
You can see the execution result of the above code first . At this time, the program is in the execution state. Because the custom thread is in the waiting queue

So how to make this thread continue to execute the remaining code? Then we must use the notify method, and then create a thread

    private Object lock;
    public OtherThread(Object lock){
        this.lock = lock;
    }
    @Override
    public void run() {
        System.out.println("线程开始工作"+Thread.currentThread().getName());
        synchronized (lock){
            lock.notify();
        }
        System.out.println("线程结束工作"+Thread.currentThread().getName());
    }
}

Here, in the synchronization code block, notify is called to initiate a notification. What notification is initiated? It is to notify those threads that call wait in a waiting state, tell them that you can execute them, and the thread that calls notify will not release the lock immediately like the thread that called wait, but will release the thread after the execution is completed Lock, and then the previously waiting thread gets the released lock and continues to execute the remaining code, so there is a point of knowledge here, that is, the lock must be the same lock, and the key point to ensure that it is the same lock is these codes Up

    public OtherThread(Object lock){
        this.lock = lock;
    }

Then execute this thread

        //同一个锁
        Object lock = new Object();
        
        MyThread myThread = new MyThread(lock);
        Thread thread = new Thread(myThread);
        thread.start();
        thread.setName("线程A");
        
        OtherThread otherThread = new OtherThread(lock);
        otherThread.start();
        otherThread.setName("线程B");

    }

Execute the program immediately and the
Insert picture description here
result is the same as the analysis!

The above is just a case of waiting for notification, that is, a thread is in a waiting state, and then a thread initiates a notification, and then the waiting thread gets the lock of the thread that initiated the notification, and then continues execution.

Of course, there is also such a situation, that is, there are many threads in the waiting state, and then one thread initiates a notification. In this case, a thread in the waiting state will be randomly notified. In fact, there is another method called notifyAll that is used to notify All the waiting threads, such goods, these threads in the waiting state, whose priority is higher, will get this notification and get the lock.

8. Inter-thread communication join (two)

First of all, what is the use of this jon, join is a method, threads can call this method, when a child thread is executed in the main thread, if the execution of the child thread ends, it will get a value, and this will be used in the main thread Value, but the actual situation is that it is very likely that the main thread has finished executing and the child thread is still executing, so that this value cannot be obtained. What should I do?

Using join can solve this problem. You only need to make the child thread call the join method, so that the main thread will be executed after the child thread is executed. To put it simply, after the child thread calls the join method, it must wait for the child thread After the execution is complete, you can do other things

It is also very simple to use

In addition, there is such a way of writing join (long), which is expressed in the code as follows

In this case, the thread will wait for 2 seconds to execute, you must know to use such code alone

It is necessary to wait until the end of the child thread before executing other code, but if this is the case

After two seconds, other code will be executed. Seeing this, this function seems a bit similar to this

But there is an essential difference between the two is that the lock will be released if join, while sleep will not. This will have specific applications in specific scenarios.

Guess you like

Origin blog.csdn.net/GTC_GZ/article/details/108680386