【2023】Java multi-threading basics and common thread classes

1. Basic concepts of processes and threads

1. The background of the process

The original computer could only accept some specific instructions. Every time the user entered an instruction, the computer performed an operation. While the user is thinking or typing, the computer is waiting. This is very inefficient. In many cases, the computer is in a waiting state.

Batch operating system

Later, there was a batch processing operating system, which wrote down a series of instructions that required operations to form a list and handed it to the computer at once. Users write multiple programs that need to be executed on a tape, and then let the computer read and execute these programs one by one, and write the output results on another tape.

The batch operating system improves the efficiency of the computer to a certain extent, but because the instruction execution mode of the batch operating system is still serial, there is always only one program running in the memory, and subsequent programs need to wait for the execution of the previous program to complete. Before execution can begin, the previous program is sometimes blocked due to I/O operations, network and other reasons, so the batch processing operation efficiency is not high.

Proposal of process

People have higher and higher requirements for computer performance. The existing batch operating system cannot meet people's needs. The bottleneck of the batch operating system is that there is only one program in the memory. So can there be multiple programs in the memory? ? This is a problem that people urgently need to solve.

Therefore, scientists proposed the concept of process.

A process is the space allocated by the application in the memory, that is, the running program. Each process does not interfere with each other. At the same time, the process saves the status of the program running at each moment.

Program: A collection of codes written in a certain programming language (java, python, etc.) that can complete a certain task or function. It is an ordered collection of instructions and data, and is a piece of static code.

At this time, the CPU uses time slice rotation to run the process: the CPU allocates a time period to each process, called its time slice. If the process is still running at the end of the time slice, the running of the process is suspended and the CPU is allocated to another process (this process is called context switching). If the process blocks or ends before the time slice ends, the CPU switches immediately without waiting for the time slice to run out.

When a process is suspended, it will save the state of the current process (process identity, resources used by the process, etc.), restore it based on the previously saved state when it switches back next time, and then continue execution.

An operating system that uses the process + CPU time slice rotation method looks macroscopically like executing multiple tasks in the same time period. In other words, the process makes the concurrency of the operating system possible. Although concurrency has multiple tasks executing from a macro perspective, in fact, for a single-core CPU, only one task is occupying CPU resources at any specific moment.

The requirements for operating systems have further increased

Although the emergence of processes has greatly improved the performance of the operating system, as time goes by, people are not satisfied that a process can only do one thing at a time. If a process has multiple subtasks, they can only be executed one by one. These subtasks greatly affect efficiency.

For example, when anti-virus software detects a user's computer, if it gets stuck on a certain test, subsequent test items will also be affected. In other words, when you use the virus scanning function in anti-virus software, you cannot use the garbage cleaning function in the anti-virus software before the virus scanning is completed, which obviously cannot meet people's requirements.

Thread proposal

So can these subtasks be executed at the same time? So people proposed the concept of threads, allowing one thread to perform a subtask, so that a process contains multiple threads, each thread is responsible for a separate subtask.

After using threads, things become much simpler. When the user uses the virus scanning function, let the virus scanning thread be executed. At the same time, if the user uses the garbage cleaning function, the virus scanning thread can be paused first, respond to the user's garbage cleaning operation, and let the garbage cleaning thread execute. After the response is completed, switch back and then execute the scanning virus thread.

Note: How the operating system allocates time slices to each thread involves thread scheduling strategies. Interested students can read "Operating System". This article will not provide an in-depth explanation.

In short, the introduction of processes and threads has greatly improved the performance of the operating system. Processes make the concurrency of the operating system possible, and threads make the internal concurrency of the process possible.

Concurrency can also be achieved through multi-processing. Why do we use multi-threading?

Multi-process mode can indeed achieve concurrency, but using multi-threads has the following benefits:

  • Communication between processes is more complex, while communication between threads is relatively simple. Usually, we need to use shared resources, which are easier to communicate between threads.
    Processes are heavyweight, while threads are lightweight, so the system overhead of multi-threading is smaller.

The difference between process and thread

A process is an independent running environment, and a thread is a task executed in a process. The essential difference between them is whether they occupy separate memory address space and other system resources (such as I/O):

  • Processes individually occupy a certain memory address space, so there is memory isolation between processes, data is separated, data sharing is complex but synchronization is simple, and each process does not interfere with each other; threads share the memory address space and resources occupied by the process to which they belong, and data Sharing is simple, but synchronization is complex.

  • A process alone occupies a certain memory address space. Problems with one process will not affect other processes or the stability of the main program, and the reliability is high; the crash of one thread may affect the stability of the entire program, and the reliability is low.

  • A process alone occupies a certain memory address space. The creation and destruction of a process not only requires saving registers and stack information, but also requires resource allocation and recycling and page scheduling, which is relatively expensive. Threads only need to save registers and stack information, which is relatively small.

Another important difference is that the process is the basic unit of resource allocation by the operating system, while the thread is the basic unit of scheduling by the operating system, that is, the unit of CPU allocation time.

2. Title context switching

Context switching (sometimes called process switching or task switching) refers to the CPU switching from one process (or thread) to another process (or thread). Context refers to the contents of the CPU registers and program counter at a certain point in time.

Registers are a small amount of fast flash memory inside the CPU. They usually store and access intermediate values ​​in the calculation process to improve the running speed of computer programs.

The program counter is a special register used to indicate
the position in the instruction sequence that the CPU is executing. The stored value is the position of the instruction being executed or the position of the next instruction to be executed. The specific implementation depends on the specific system.

Example of threads A - B

1. First suspend thread A and save its state in the CPU in memory.

2. Retrieve the context of the next thread B in the memory and restore it in the CPU register, and execute the B thread.

3. When B finishes executing, resume thread A according to the position pointed to by the program counter.

The CPU implements the multi-threading mechanism by allocating CPU time slices to each thread. The CPU executes tasks cyclically through a time slice allocation algorithm. After the current task executes a time slice, it will switch to the next task.

However, the status of the previous task will be saved before switching, so that the status of this task can be loaded again the next time you switch back to this task. So the process of a task from saving to reloading is a context switch.

Context switching is usually computationally intensive, meaning that this operation consumes a lot of CPU time, so more threads are not always better . How to reduce the number of context switches in the system is a key issue to improve multi-threaded performance.

2. Java multi-threading entry classes and interfaces

No return

First, we need to have a "thread" class. JDK provides the Thread class and Runnable interface to allow us to implement our own "thread" class.

  1. Inherit the Thread class and override the run method;
  2. Implement the run method of the Runnable interface;
  • Thread class
public class Demo {
    
    
    public static class MyThread extends Thread {
    
    
        @Override
        public void run() {
    
    
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
    
    
        Thread myThread = new MyThread();
        myThread.start();
    }
}

Note that the thread is not started until the start() method is called!

After we call the start() method in the program, the virtual machine first creates a thread for us, and then waits until this thread gets the time slice for the first time before calling the run() method.

Note that the start() method cannot be called multiple times. After calling the start() method for the first time, calling the start() method again will throw an IllegalThreadStateException.

  • Runnable interface
public class Demo {
    
    
    public static class MyThread implements Runnable {
    
    
        @Override
        public void run() {
    
    
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
    
    

        new Thread(new MyThread()).start();

        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
    
    
            System.out.println("Java 8 匿名内部类");
        }).start();
    }
}

There is return

Callable, Future and FutureTask
Generally speaking, we use Runnable and Thread to create a new thread. But they have a drawback, that is, the run method has no return value. Sometimes we want to start a thread to perform a task, and have a return value after the task is completed.

JDK provides the Callable interface and Future interface to solve this problem for us, which is also the so-called "asynchronous" model.

  • Callable interface
// 自定义Callable
class Task implements Callable<Integer>{
    
    
    @Override
    public Integer call() throws Exception {
    
    
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
    
    
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法。
        System.out.println(result.get());
    }
}
  • Future interface
    The Future interface has only a few relatively simple methods:

cancelThe method is to try to cancel the execution of a thread.

Note that although you are trying to cancel, the cancellation may not be successful . Because the task may have been completed, canceled, or cannot be canceled due to some other factors, there is a possibility of cancellation failure. booleanThe return value of the type means "whether the cancellation was successful". The parameter paramBooleanindicates whether to cancel thread execution by interrupting.

So sometimes, in order to allow the task to be cancelled, it is used Callableinstead Runnable. If used for cancelability Futurebut without providing a usable result, you can declare Future<?>a formal type and return null as the result of the underlying task.

- FutureTask class

// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
    
    
    @Override
    public Integer call() throws Exception {
    
    
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
    
    
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

Guess you like

Origin blog.csdn.net/weixin_52315708/article/details/131522011