Consolidate the foundation of multi-threading in 10 minutes

Consolidate the foundation of multi-threading in 10 minutes

Preface

Multithreading is the basis of concurrent programming. This article will talk about multithreading.

Let’s talk about concepts first, such as processes and threads, seriality, parallelism and concurrency.

Let’s talk about thread status, priority, synchronization, communication, termination and other knowledge

Processes and Threads

What is a process?

The operating system allocates resources to processes and uses processes for scheduling. However, when a process encounters a blocking task, it will switch processes in order to improve CPU utilization.

Because the cost of switching processes is too high, threads were born

Threads are also called lightweight processes (LWP). Threads are the basic scheduling unit of the operating system. When threads are assigned to time slices given by the CPU, they can schedule tasks.

When a thread is blocked while waiting for a resource, the thread will be suspended in order to improve CPU utilization. When subsequent resources are ready, the thread will be resumed and execution will continue after being assigned a time slice.

For the sake of safety, threads are divided into user mode and kernel mode. When using threads to operate ordinary tasks, you can schedule execution in user mode. When you want to complete certain operations related to operating system security, you need to switch to kernel mode first before proceeding. operate

The suspension and resumption of threads require switching between user mode and kernel mode. Frequent switching of threads will also bring certain overhead.

When we click to open the browser, the browser program may start one or more processes

There are one or more threads under a process. The process is used to manage the resources allocated by the operating system. The thread is used for scheduling. All threads under the same process can share the resources of the process . In order to store the scheduled task running status, the thread It will also have its own private memory space to store it.

There are three types of thread model implementations in user mode and kernel mode: one-to-one between user threads and kernel threads, many-to-one and many-to-many.

one-to-one model

The one-to-one model is simple to implement. One user thread maps to one kernel thread. The model used in Java is one-to-one.

image.png

However, if threads are used improperly, it may lead to frequent switching of kernel states, resulting in a lot of overhead.

And the kernel thread resources are limited , so there is an upper limit on thread resources in the one-to-one model.

many to one

In many-to-one model

image.png

Since multiple user threads map the same kernel thread, more user threads can be used compared to the one-to-one model.

However, when blocking occurs, you need to switch to the kernel state for blocking. All user threads corresponding to the kernel thread will be blocked , and its implementation will also become complicated.

many to many

In a many-to-many model

image.png

It not only solves the thread upper limit problem of the one-to-one model, but also solves the problem of kernel thread blocking corresponding to all user threads blocking in the many-to-one model.

But the implementation becomes more complex

Serial, Parallel and Concurrent

Why use multithreading?

With the development of hardware, most machines are no longer single-core CPU machines. A large number of machines use multi-core hyper-threading technology.

Serialization can be understood as queuing execution. When the thread is allocated CPU resources, scheduling begins. The thread may schedule IO tasks.

At this time, it will wait for the IO resources to be ready before scheduling. During this period, the CPU does nothing and the CPU is not effectively utilized.

image.png

In order to improve CPU utilization, when thread A is waiting for IO resources, thread A can be suspended first and allocate CPU resources to thread B.

When the IO resource that thread A is waiting for is ready, thread B is suspended and thread A is resumed to continue execution.

The two threads appear to be executing at the same time for a period of time. In fact, they execute alternately. Only one thread is executing at a certain time.

Concurrency improves CPU utilization, but it also brings the overhead of thread context switching.

image.png

So what is parallelism?

The serialization and concurrency mentioned above can be achieved in a single thread, but the prerequisite for parallelism is multi-core

Parallelism means that multiple threads are executed at the same time at a certain time, so multiple cores are required.

image.png

So is multi-threading necessarily the fastest?

After the above analysis, we know that: thread suspension and recovery, context switching will go through the conversion of user mode and kernel mode, and there will be performance overhead.

When there are too many threads and frequent context switching occurs during runtime, the performance overhead may even exceed the benefits of concurrency improvement in CPU utilization.

Create thread

The thread class provided to us in JDK is java.lang.Threadthat it implements the Runnable interface and uses a construct to accept the implementation of Runnable

  public class Thread implements Runnable {
      private Runnable target;
  }

The Runnable interface is a functional interface, which only has a run method. The implementation in the run method indicates the tasks to be performed after the thread is started.

  public interface Runnable {
      public abstract void run();
  }

There is only one way to create a thread in Java: create a Thread object, and then call the start method to start the thread.

We can create a thread through the constructor and set the name of the thread at the same time, and set the task to be implemented (printing thread name + hello)

      public void test(){
          Thread a = new Thread(() -> {
              //线程A hello
              System.out.println(Thread.currentThread().getName() + " hello");
          }, "线程A");
          //main hello
          a.run();
          a.start();
      }

When the run method is called in the main thread, it is actually the main thread that performs the tasks of the runnable interface.

As we said before, the thread model in Java is a one-to-one model, and one thread corresponds to one kernel thread.

Only when the start method is called, the local method (C++ method) is called to start the thread to execute the task.

image.png

If start is called twice, IllegalThreadStateExceptionan exception will be thrown.

Thread status

The status of Thread in Java is divided into new, running, blocking, waiting, timeout waiting, and termination.

 public enum State {
     //新建
     NEW,
     //运行
     RUNNABLE,
     //阻塞
     BLOCKED,
     //等待
     WAITING,
     //超时等待
     TIMED_WAITING,
     //终止
     TERMINATED;
 }

In the operating system, running is divided into ready and running states. After the thread is created, the state waiting for the CPU to allocate a time slice is the ready state, and the state allocated to the time slice is the running state.

image.png

New: The thread has just been created and has not yet obtained the time slice allocated by the CPU.

Run: The thread obtains the time slice allocated by the CPU and performs task scheduling

Blocking: During the thread scheduling process, it enters a blocked state due to the inability to obtain shared resources (such as being blocked by synchronized)

Waiting: During the thread scheduling process, execute wait, join and other methods to enter the waiting state and wait for other threads to wake up.

Timeout waiting: During the thread scheduling process, the timeout waiting state is entered when executing sleep(1), wait(1), join(1) and other methods to set the waiting time.

Termination: The thread completes the scheduled task or executes abnormally and enters the termination state.

priority

The prerequisite for a thread to schedule tasks is to obtain CPU resources (time slice allocated by the CPU)

Provides methods in Java setPriorityto set the priority for obtaining CPU resources. The range is 1~10, and the default is 5.

  //最小
  public final static int MIN_PRIORITY = 1;
 
  //默认
  public final static int NORM_PRIORITY = 5;
  
  //最大
  public final static int MAX_PRIORITY = 10;

But the priority set is only at the Java level, and the priority mapped to the operating system is different.

For example, setting priority 5 or 6 in Java may map to the priority of the operating system at the same level.

daemon thread

What is a daemon thread?

Daemon threads can be understood as background threads. When all non-daemon threads in the program have completed their tasks, the program will end.

In short, regardless of whether the daemon thread has finished executing, as long as the non-daemon thread has finished executing, the program will end

Therefore, the daemon thread can be used to perform some background operations for checking resources.

How to use setDaemon(true)to turn a thread into a daemon thread

Thread synchronization

When multiple threads need to use shared resources, they cannot obtain them at the same time due to the limited number of shared resources.

Only one thread can obtain the shared resource at a time, and other threads that have not obtained the shared resource need to be blocked.

Logic errors may occur if multiple threads use shared resources at the same time.

The synchronized keyword is commonly used in Java to use locking to ensure synchronization (only one thread can access shared resources)

         synchronized (object){
             System.out.println(object);
         }

where object is a locked shared resource

For more descriptions of synchronized, you can check out this article: 15,000 words, 6 code cases, and 5 schematics to help you thoroughly understand Synchronized

Thread communication

Wait/notify notify

When using synchronized, you need to acquire the lock. Only after acquiring the lock can the thread execute the schedule. When the execution conditions are not met in the schedule, the lock needs to be given up to allow other threads to execute.

For example, in the producer/consumer model, when the producer obtains the lock to produce resources and finds that the resources are full, it should give up the lock and wake it up when the consumer has finished consuming.

This wait/notify mode is a way to implement thread communication. Java provides wait and notify methods to implement the wait/notify mode.

The prerequisite for using wait and notify is to obtain the lock

wait allows the current thread to release the lock and enter waiting mode, waiting for other threads to wake up using notify

wait(1) can also carry the waiting time ms. When the time arrives, it will automatically wake up and start competing for the lock.

notify wakes up a thread waiting for the current lock

notifyAll wakes up all threads waiting for the current lock

For its specific implementation, you can view 15,000 words, 6 code cases, and 5 schematic diagrams to fully understand the heavyweight lock section of Synchronized's lock upgrade.

producer consumer model

Waiting and notification are commonly used in producer and consumer models for thread communication.

When the producer checks that the produced resources are full, it enters waiting, waits for the consumer to wake up after consumption, and then wakes up the consumer after production is completed.

When the consumer checks that there are no resources, it enters a waiting state, waits for the producer to wake up after production, and then wakes up the producer after consumption.

Production

public void produce(int num) throws InterruptedException {
    synchronized (LOCK) {
        //如果生产 资源 已满 等待消费者消费
        while (queue.size() == 10) {
            System.out.println("队列满了,生产者等待");
            LOCK.wait();
        }
        
        Message message = new Message(num);
        System.out.println(Thread.currentThread().getName() + "生产了" + message);
        queue.add(message);
        //唤醒 所有线程
        LOCK.notifyAll();
    }
}

Consumption

public void consume() throws InterruptedException {
    synchronized (LOCK) {
        //如果队列为空 等待生产者生产
        while (queue.isEmpty()) {
            System.out.println("队列空了,消费者等待");
            LOCK.wait();
        }
        Message message = queue.poll();
        System.out.println(Thread.currentThread().getName() + "消费了" + message);
        //唤醒 所有线程
        LOCK.notifyAll();
    }
}

sleep sleep

The sleep method is used to let the thread sleep for a period of time ms

The difference from wait is that sleep does not release the lock when sleeping, and there is no need to acquire the lock first when using sleep.

join wait

The join method is used to wait for a thread to finish executing

For example, if called on the main thread, thread.join()you need to wait for the thread thread to finish executing before the join method returns.

At the same time, join also supports setting the waiting time ms and automatically returns after timeout.

Terminate thread

To terminate a thread, generally use a safe termination method: interrupt the thread

When the thread is running, a flag bit will be saved, which defaults to false, indicating that no other thread will interrupt it.

When you want a thread to stop, you can interrupt it. For example 线程A.interrupt(): perform an interrupt operation on thread A. At this time, the interrupt flag of thread A is true.

When a thread schedules a task, it will stop polling when the interrupt flag is true. You can use 线程A.isInterrupted(): View the interrupt flag of thread A

When a thread enters the waiting state and is interrupted by other threads, an interrupt exception will occur. The flag bit will be cleared and an interrupt exception will be thrown; it can be captured in the catch block to clean up resources or release resources.

When executing in a loop according to the interrupt identifier, you can also interrupt yourself to stop and continue execution.

         Thread thread = new Thread(() -> {
             //中断标识为false就循环执行任务
             while (!Thread.currentThread().isInterrupted()) {
                 try {
                     //执行任务
                     System.out.println(" ");
                     
                     //假设等待资源
                     TimeUnit.SECONDS.sleep(1);
                     
                     //获得资源后执行
                     
                 } catch (InterruptedException e) {
                     //等待时中断线程会在抛出异常前恢复标志位
                     //捕获异常时,重新中断标志(自己中断)
                     Thread.currentThread().interrupt();
                     
                     //结束前处理其他资源
                 }
             }
             // true
             System.out.println(" 中断标识位:" + Thread.currentThread().isInterrupted());
         });

There is another way to detect interrupts Thread.interrupted(): check the interrupt mark of the current thread, clear the interrupt mark of the current thread, and return the interrupt mark to false.

Finally (don’t do it for free, just press three times in a row to beg for help~)

This article is included in the column " From Point to Line, and from Line to Surface" to build a Java concurrent programming knowledge system in simple terms . Interested students can continue to pay attention.

The notes and cases of this article have been included in gitee-StudyJava and github-StudyJava . Interested students can continue to pay attention under stat~

Case address:

Gitee-JavaConcurrentProgramming/src/main/java/A_Thread

Github-JavaConcurrentProgramming/src/main/java/A_Thread

If you have any questions, you can discuss them in the comment area. If you think Cai Cai’s writing is good, you can like, follow, and collect it to support it~

Follow Cai Cai and share more useful information, public account: Cai Cai’s back-end private kitchen

This article is published by OpenWrite, a blog that publishes multiple articles !

Lei Jun: The official version of Xiaomi’s new operating system ThePaper OS has been packaged. A pop-up window on the Gome App lottery page insults its founder. The U.S. government restricts the export of NVIDIA H800 GPU to China. The Xiaomi ThePaper OS interface is exposed. A master used Scratch to rub the RISC-V simulator and it ran successfully. Linux kernel RustDesk remote desktop 1.2.3 released, enhanced Wayland support After unplugging the Logitech USB receiver, the Linux kernel crashed DHH sharp review of "packaging tools": the front end does not need to be built at all (No Build) JetBrains launches Writerside to create technical documentation Tools for Node.js 21 officially released
{{o.name}}
{{m.name}}

Guess you like

Origin my.oschina.net/u/6903207/blog/10114995