Implementation of Threads

There are three main ways to implement threads:

  • It is implemented using kernel threads
  • It is implemented using user threads
  • A hybrid implementation of user threads and lightweight processes

It is implemented using kernel threads

Kernel-level threads (KLT) are directly supported by the Kernel of the operating system. This kind of Thread is completed by the Kernel to switch the threads. The Kernel manipulates the scheduler to schedule the threads, and is responsible for mapping the tasks of the threads to various processors. Each kernel thread can be thought of as a clone of the kernel, so that the operating system has the ability to handle multiple things at once. Kernels that support multiple threads are called multithreaded kernels. Programs generally do not use kernel threads directly, but use a high-level interface of kernel threads — lightweight processes. Lightweight processes are usually referred to as threads. Since each lightweight process is supported by a kernel thread, only kernel threads can be supported before lightweight processes can be created. The 1:1 relationship between the lightweight process and the kernel thread is called the one-to-one threading model. The diagram is as follows:



Due to the support of kernel threads, each lightweight process becomes a separate scheduling unit. Even if a lightweight process is blocked in a system call, it will not affect the whole process to continue working. However, lightweight processes have their limitations:

  • First, because the implementation is based on kernel threads, various thread operations, such as creation, destructing, and synchronization, require system calls. The cost of system call is relatively high, which requires switching back and forth between user state and kernel state.
  • Second, each lightweight process needs to be supported by a kernel thread, so lightweight processes consume a certain amount of kernel resources, so there is a limit to the number of lightweight processes supported by a system.

It is implemented using user threads

In a broad sense, as long as a thread is not a kernel thread, it can be considered as a user thread. From this definition, lightweight processes are also user threads, but the implementation of lightweight processes is always built on the kernel, many operations need to make system calls, and efficiency is recycled to the limit. In the narrow sense, user threads are implementations that are entirely based in user space, and the kernel is not aware of the existence of threads. The creation, synchronization, destruction, and scheduling of user threads are done entirely in user mode, without the kernel’s help. If the program is implemented properly, such threads do not need to switch to kernel state, so operations can be very fast and inexpensive, or they can support a larger number of threads. Some of the multi-threading in high-performance databases is implemented by user-state threads. This 1:N relationship between the process and the user thread is called the one-to-many thread model. The diagram is as follows:



The advantage of using user threads is that there is no kernel support, and the disadvantage is that there is no kernel support, all thread operations need to be handled by the user program itself.

A hybrid implementation of user threads and lightweight processes

In addition to relying on the kernel thread implementation and being completely implemented by the user program itself, there is an implementation that uses the kernel thread together with the user thread. In this hybrid mode, there are user threads and lightweight processes. User threads are still built entirely in user space, so user thread creation, destructing, switching, and so on are still cheap and can support large-scale user thread concurrency. The lightweight process supported by the operating system acts as a bridge between the user thread and the kernel thread, so that the kernel can use the thread scheduling function that is processor mapping, and the system call of the user thread should be completed through the lightweight process, which greatly reduces the risk of the whole process being completely blocked. The ratio of user threads to lightweight processes in this hybrid mode is variable, with an N:M relationship, in this many-to-many threading model. The diagram is as follows:

Java Thread Scheduling

Thread scheduling refers to the process in which the system allocates processor use rights to threads. There are two main scheduling methods, namely cooperative thread scheduling and preemptive thread scheduling.

If you use a multi-threaded system with co-scheduling, the execution time of the thread is controlled by the thread itself. When the thread has finished its work, it needs to actively inform the system to switch to another thread. The main advantage of cooperative multithreading is that it is easy to implement, and because the thread does not switch until it has finished its work, the switch operation is known to the thread itself, so there is no thread synchronization problem. However, its disadvantages are also obvious. Thread execution time can not be controlled, and even if a thread is written in a bad way and the system is not informed of the thread switch, then the program will always block there.

If you use a multithreaded system with preemptive scheduling, each thread will be allocated execution time by the system, and the thread switch will not be determined by the thread itself. In this way of implementing thread scheduling, the execution time of the thread is controllable, and there will not be a problem that one thread causes the whole process to block. Java uses thread scheduling method is preemptive scheduling.

State switch

The Java language defines five thread states, and at any given point in time, a thread can have only one of them. The five states are as follows:

  • Threads that have not been started since the creation of New are in this state.
  • Runable includes the Running and Ready states of the operating system thread, meaning that the thread in this state may be executing or waiting for the CPU to allocate execution time to it.
  • Threads in this state are not allocated execution time by the CPU, they are Waiting to be visibly woken up by other threads. The following method causes the thread to wait indefinitely:

    • Object.wait() method with no Timeout parameter set
    • Thread.join() method with no Timeout parameter set
    • LockSupport. Park () method
  • Threads in this state are also not allocated CPU execution time, but instead of Waiting to be explicitly woken up by another thread, they are automatically woken up by the system after a certain amount of time. The following method causes the thread to enter the deadline waiting state:

    • Thread.sleep () method
    • The Object.wait() method with the Timeout parameter set
    • Thread.join() method with the Timeout parameter set
    • LockSupport. ParkNanos () method
    • LockSupport. ParkUntil () method
  • A Blocked thread is Blocked. The difference between a Blocked state and a waiting state is a Blocked state waiting for an exclusive lock to be acquired, an event that occurs when another thread abandons the lock, and a waiting state waiting for a period of time, or for a wake-up action to occur. The thread enters this state while the program waits to enter the synchronized region.
  • The thread state of the Terminated thread, which has Terminated execution.

The above 5 states will be converted to each other when a specific event occurs, and their conversion relationship is shown as follows: