[Multithreading Series-01] In-depth understanding of the relationship between processes, threads and CPUs

Multi-threaded series overall column


content link address
[1] In-depth understanding of the relationship between processes, threads and CPUs https://blog.csdn.net/zhenghuishengq/article/details/131714191
[2] How many ways does java create threads? (detailed) https://blog.csdn.net/zhenghuishengq/article/details/127968166
[3] In-depth understanding of the life cycle of threads in java, task scheduling https://blog.csdn.net/zhenghuishengq/article/details/131755387

1. In-depth understanding of the relationship between processes, threads and CPUs

1. Process and thread

1.1, the relationship between process and thread

In official terms, a process is a running activity of a program in a computer on a certain data set. It is the basic unit for system resource allocation and the basis of the operating system structure. The app we often say is a process. When a program is run and the code of the program is loaded from the disk into the memory, a process is started. As shown in the figure below, you can see the status of these processes by opening the resource manager, which includes some memory, disk, cpu operations, io operations, etc.

insert image description here

Thread refers to: the smallest unit that the operating system can schedule .

insert image description here

A process can survive independently, but a thread must depend on a process, that is, a process can contain multiple threads.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-btnZbPDZ-1689264816662)(img/1689053870099.png)]

1.2, the relationship between processes and threads in java

In C language, a process can have no threads, but in Java language, a process will contain at least one process. Java needs to be executed by a virtual machine. Since the jvm itself is a process, there is a simple way to view information about internal threads. Check the following method, in the main method with only one main thread, use this ThreadMXBean class to obtain the information of all current threads

/**
 * @Author: zhenghuisheng
 * @Date: 2023/7/11 13:50
 * 单线程总统计
 */
public class ThreadCount {
    
    
    public static void main(String[] args) {
    
    
        // 获取线程管理bean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 获取线程和线程堆栈信息
        ThreadInfo[] threadInfo = threadMXBean.dumpAllThreads(false, false);
        for (int i = 0; i < threadInfo.length; i++) {
    
    
            ThreadInfo ti = threadInfo[i];
            //打印线程id
            System.out.println("线程id为:" + ti.getThreadId() + "线程名称为:" + ti.getThreadName());
        }
    }
}

The printed log is as follows, so it can be found that only one main thread was executed, and there were 5 more threads when printing the log. Some reference threads, garbage collection, monitors, listeners and other threads will be started by default inside the jvm. So in java, a process has at least one thread

线程id为:6线程名称为:Monitor Ctrl-Break      //监控中断信号的
线程id为:5线程名称为:Attach Listener 		//监听内存dump,类信息统计,获取系统属性等
线程id为:4线程名称为:Signal Dispatcher		//分发处理发送给JVM信号的线程
线程id为:3线程名称为:Finalizer				//调用finalize方法的线程
线程id为:2线程名称为:Reference Handler		//清除Reference线程
线程id为:1线程名称为:main					

2. Communication between processes

The communication of the same computer is called IPC, and the communication of different computers is called RPC, which needs to go through the network and abide by the common protocol. There are several ways to communicate between processes: pipes, signals, message queues, shared memory, semaphores, sockets

2.1, pipeline

Pipes are divided into anonymous pipes and named pipes . Anonymous pipes are mainly used for communication between parent and child processes. For example, if a thread forks an independent child thread, then this thread and this child thread can communicate through anonymous pipes.

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-WwF3dWdZ-1689264816665)(img/1689060215475.png)]

Named pipes allow communication between unrelated processes based on anonymous pipes.

2.2, signal

As in the main main thread above, it can be found that there will be a Signal Dispatcher signal distribution thread after execution , which is mainly used to send and receive signals, similar to a software-level interrupt , which is consistent with the interrupt effect of the processor, and can be notified Another process is about to have an event happen

2.3, message queue

This message queue, like rabbitMq, is a linked list in memory. In this way, the shortcomings of the limited semaphore of the previous two communication methods are overcome. It is responsible for putting this signal into the message queue while being responsible for receiving the semaphore

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-nZkzHzKh-1689264816668)(img/1689061371668.png)]

2.4, shared memory (emphasis)

The shared memory method can be said to be the most useful inter-process communication method. It is to open up a shared space. Each process can access and modify this memory space. Different processes can see the shared data in the other process in time. update. This method needs to rely on mutex or semaphore operations to solve possible concurrency problems.

There are three processes abc as follows, all of which can operate the common physical memory space above at the same time, so as to realize the way of shared memory

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-Ie9A5NFh-1689264816669)(img/1689061549673.png)]

2.5, signal amount

It is mainly realized through this clh synchronous waiting queue, mainly as a means of synchronization and mutual exclusion between processes and between different threads of the same process

2.6, sockets

It is a socket socket, which is generally used for inter-process communication between different machines in the network, and its application is relatively extensive. And in the same machine, Unix domain socket can be used . This method does not need to go through the network protocol stack, pack and unpack, calculate the checksum, maintain the serial number and reply, etc., which is more efficient than purely network-based inter-process communication. . For example, a typical mysql, whether it is connected from the console or from other machines, because it uses the socket connection method internally, its implementation is the same as that of this network, so its internal and local machines can be shared a set of codes. The types of sockets are mainly divided into: stream sockets, datagram sockets, and raw sockets .

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-ikKUhYQe-1689264816670)(img/1689063439290.png)]

3. The relationship between the number of CPU cores and the number of threads

Each process must use a shared cpu. Currently, mainstream CPUs are multi-core, and a thread is the smallest unit of CPU scheduling. That is to say, a core CPU can only run one thread. However, after Intel introduced this hyper-threading technology, the The concept of logical processors is introduced, so that two threads can be executed in one CPU, so that the relationship between the number of CPUs and the number of threads reaches 1:2 . As shown in the figure below, you can see that the core is 2 and the logical processor is 4

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-hqsUNJIk-1689264816673)(img/1689078247767.png)]

You can also directly check the number of CPUs of the current machine through the java code, and the displayed result is the number of corresponding logical processors

//获取的结果是4,而不是2
Runtime.getRuntime().availableProcessors();

When using the thread pool, you can set the maximum number of cores according to whether it is io-intensive or cpu-intensive. Generally, the maximum number of core threads for IO-intensive type is 2N, and the maximum number of core threads for CPU-intensive type is N or N+1, where N refers to the number of logical processors .

//获取当前机器的逻辑处理器的个数
int n = Runtime.getRuntime().availableProcessors();
//创建io密集型线程池
new ThreadPoolExecutor(
        n*2 - 2,n*2,5L,TimeUnit.SECONDS,new LinkedBlockingDeque<>()
);
//创建cpu密集型线程池
new ThreadPoolExecutor(
        n - 1,n,5L,TimeUnit.SECONDS,new LinkedBlockingDeque<>()
);

4. Context switching

The operating system needs to schedule tasks among multiple processes, and each thread always uses resources in the CPU when using the CPU. Therefore, in order to ensure the normal execution of threads before and after scheduling, the operating system needs a context switching operation. , refers to switching from one process or thread to another process or thread

The cpu also contains some caches, registers, program counters, etc. When the cpu switches from one thread to another, the data stored in these attributes corresponds to the internal data of the thread. As for the data of the previous thread, if the thread task has not been executed, it will be saved through the CPU register or the program counter . When switching to the thread next time, it only needs to continue to execute from the moment it ends. For example, in the program counter of jvm, the bytecode instruction of the corresponding thread will be saved, and when the CPU polls and switches to the thread, it will be executed directly from the corresponding line number

Context switching can be described in more detail as the activity of the kernel state on the process or thread on the cpu: suspend the execution of the current process or thread, store the CPU state at this time, and then obtain the context of a new process or thread, in Restored in CPU registers

Causes of cpu context switching mainly include: process, thread switching, thread scheduling, etc.

5. Threads in java

5.1, the way to create threads

As mentioned in the official jdk of java, there are two main ways to create threads: There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread . The other way to create a thread is to declare a class that implements the Runnable interface.

That is to say: There are two ways to create threads, one is to inherit Thread, and the other is to implement the Runnable interface .

public class ThreadTest extends Thread {
    
    
    @Override
    public void run() {
    
    
    }
}

Create a thread by implementing runnable, and then use this instance as a parameter of new Thread

public class ThreadCreate implements Runnable {
    
    
    @Override
    public void run() {
    
    
    }
}
public class Main(){
    
    
    public static void mian(String args[]){
    
    
        ThreadCreate threadCreate = new ThreadCreate();
        Thread thread = new Thread(threadCreate);
    }
}

Both of the above two methods need to create threads through the Thread class, but these Thread methods are all void types, that is to say, they will not return values, so there is a method of creating threads called callable

[External link picture transfer failed, the source site may have an anti-leeching mechanism, it is recommended to save the picture and upload it directly (img-w7pbFcX9-1689264816674)(img/1689255296762.png)]

The way to get the return value by implementing callable is as follows

public class ThreadCreate1 implements Callable {
    
    
    @Override
    public Object call() throws Exception {
    
    
        return null;
    }
}

However, in the method of creating a thread through Thread , there is no method that supports callable as a parameter, so it is necessary to convert callable into runnable. Here, an important class FutureTask is needed, and its class diagram is as follows
insert image description here

The parameter in futureTask happens to be this callable, so this callable can be converted into Runnable by packaging

public FutureTask(Callable<V> callable) {
    
    
    if (callable == null)
        throw new NullPointerException();
    this.callable = callable;
    this.state = NEW;       // ensure visibility of callable
}

The way to convert callable to Runnable is as follows, convert this FutureTask upwards to Runnable, and then use this Runnable as a parameter of Thread, so that a thread can be created, and the return value can also be obtained through this futureTask.get ( ) task return value

public class ThreadCreate1 implements Callable {
    
    
    @Override
    public void run() {
    
    
    }
}
public class Main(){
    
    
    public static void mian(String args[]){
    
    
        ThreadCreate1 threadCreate1 = new ThreadCreate1();
        //向上转型
        //Runnable futureTask = new FutureTask(threadCreate1);
        FutureTask futureTask = new FutureTask(threadCreate1);
        Thread thread = new Thread(futureTask);
        //获取任务返回的结果
        futureTask.get();
    }
}

5.2, Thread start and stop

5.2.1, thread start

The start of the thread is relatively simple, that is, after the thread is created, it can be started by calling the start method. The start() method allows a thread to enter the ready queue to wait for the CPU to be allocated. After the CPU is allocated, the implemented run() method is called. The start() method It cannot be called repeatedly, and an exception will be thrown if it is called repeatedly

ThreadCreate threadCreate = new ThreadCreate();
Thread thread = new Thread(threadCreate);
thread.start();

Since a Thread is just an object in java, after calling start, a local method will be called

private native void start0();

That is to say, an object will correspond to a local thread, so a thread cannot be started twice at the same time, and an exception will be thrown if it is started twice

thread.start();
//抛出异常
thread.start();

In java, the run method is actually only an ordinary method, which can also be called directly, but through the printed thread, it can be found that the thread executing the run method is not the current thread, but the main method main thread

public class ThreadCreate implements Runnable {
    
    
    @Override
    public void run() {
    
    
        System.out.println("当前线程名称为" + Thread.currentThread().getName());
    }
}
public class Main(){
    
    
    public static void mian(String args[]){
    
    
        ThreadCreate threadCreate = new ThreadCreate();
        //直接执行run方法
        thread.run();
    }
}

insert image description here

5.2.2, Thread Termination

Thread termination can be divided into many situations: one is natural termination , such as the execution of the entire task in the run method, or when the code is abnormal, it will also terminate; the other is the stop of interruption , such as calling this Thread The .interrupt() method.

Natural termination can also be manually calling some methods such as stop() , suspend() , resume(), etc., but there is a Deprecated expiration annotation on these methods, indicating that they have been abandoned. The main reason is that although these methods can allow the operating system to perform a stop or suspend operation, these suspended or stopped threads will not release resources during this process . For example, the stop method is a powerful method to end a thread, but because of too much force, it will not release other resources such as cpu, or cause file damage; suspend is a method for suspending a thread, and it will not be suspended during the suspended phase. Release resources, such as some locks, files, etc., there will be problems such as deadlocks.

Therefore, in order to stop the thread more gracefully and release resources, this interrupted method is introduced. If the thread1 thread notifies the thread2 thread that it is about to be interrupted, then thread2 will first judge whether a flag bit is true after receiving this signal, and if it is true, it will first perform an operation to release resources, and then interrupt

public static boolean interrupted() {
    
    
    //检查标志位是否为true
    return currentThread().isInterrupted(true);
}

interrupt is used to interrupt the operation, and the interrupted method is used to judge whether an interrupt is required, that is, it can also be uninterrupted, that is, the interrupt request is ignored, and the resource will be released first, and then a suspended or stopped work will be performed.

//设置中断的标志位为true
thread.interrupt();

In general, after setting the flag bit, you need to check the flag bit through the code. The flag bit is false by default. After calling the interrupt method, the flag bit will be changed to true. If you don’t check the flag in the thread bit, then the thread executes as before. If you check the thread flag bit, by checking the isInterrupted method, if it is true, the current thread will release the resource first, and then suspend or stop in between.

public class ThreadCreate implements Runnable {
    
    
    @Override
    public void run() {
    
    
        Thread thread = Thread.currentThread();
        //用于判断标志位
        while (!thread.isInterrupted()){
    
    
            System.out.println("线程没被中断");
        }
    }
}
public class ThreadCount {
    
    
    public static void main(String[] args) {
    
    
        ThreadCreate threadCreate = new ThreadCreate();
        Thread thread = new Thread(threadCreate);
        //线程中断,将标志位改为true
        thread.interrupt();

    }
}

In other words, thread.interrupt() and thread.isInterrupted() should be used in combination. And this flag bit is best to use this thread.isInterrupted() as the judgment condition, it is best not to customize the flag bit, so as to avoid the change of this flag bit not being detected quickly due to blocking. If there is a deadlock, the thread cannot be stopped using blocking

Guess you like

Origin blog.csdn.net/zhenghuishengq/article/details/131714191