Java has been developed for so long, are you sure you know the basics of these threads?

Those who are familiar with Java development know that Java naturally supports multi-threaded programming. In this article, we will mainly learn the basics of Java threads, from thread startup to the communication methods between different threads, with the goal of grasping the basics of Java threads more systematically.

The explanation of this article mainly starts from the following points:

  1. What is thread
  2. What are the states of threads
  3. Start and termination of threads
  4. Communication between threads
  5. Use the thread knowledge explained in this article to implement a simple thread pool

If you have mastered the points listed above, then this article may not be able to help you. Look down selectively.

ok, start to step into today’s topic

[External link image transfer failed. The source site may have an anti-leech link mechanism. It is recommended to save the image and upload it directly (img-Zo0oaNHN-1597519114515)(en-resource://database/2540:1)]

What is thread

The following is the concept of processes and threads in the operating system:

When a modern operating system runs a program, it creates a process for it. For example, start a Java program, the operating system will create a Java process. The smallest unit of modern operating system scheduling is the thread, also called the Light Weight Process. Multiple threads can be created in a process. These threads have their own counters, stacks, and local variables, and can access them. Shared memory variables. The processor switches on these threads at high speed, so that the user feels that these threads are executing at the same time.

So what is a thread in Java?

Threads in Java

In Java, "thread" refers to two different things:

  1. An instance of the java.lang.Thread class;
  2. Thread execution

Use java.lang.Thread class or java.lang.Runnable interface to write code to define, instantiate and start a new thread. An instance of the Thread class is just an object, like any other object in Java, with variables and methods that live and die on the heap.

A Java application always runs from the main() method. The mian() method runs in a thread, which is called the main thread. Once a new thread is created, a new call stack is generated.

It should be noted that there is a special thread in Java, that is, the Daemon thread .

Daemon thread is a supporting thread, because it is mainly used for background scheduling and supporting work in the program. This means that when there are no non-Daemon threads in a Java virtual machine, the Java virtual machine will exit. The thread can be set as a Daemon thread by calling Thread.setDaemon(true). The Daemon attribute needs to be set before the thread is started, and cannot be set after the thread is started.

Thread status

Java threads are divided into 6 states in the entire life cycle. At any given moment, the thread can only be in one state.

State name Description
NEW As the name implies, the initial state, the start method has not been called after being created
RUNNABLE Running state, Java threads collectively refer to the ready and running in the operating system as "running"
BLOCKED Blocked state, which means that the thread is blocked in a lock, such as mutually exclusive resources of synchronized life
WAITING Waiting state, entering this state means that other threads need to do some specific operations (such as notification or interrupt
TIME_WAITING The timeout waiting state is different from WAITING in that it can return by itself at a specified time
TERMINATED Termination status. Indicates that the current thread has completed execution

Interested students can use the jstack tool to view the thread information of the running code under hands-on practice, which is much more useful than just looking at it, and can have a deeper understanding of the various states of the thread.

The sample code provided here is as follows, and the code in the sample has been uploaded to github. Students who need it can get it from this address
https://github.com/coderluojust/qige_blogs

Insert picture description here

Here are the steps to view using jstack:

  1. Run the above example, open a terminal or command prompt, enter jps, and find the corresponding process number;
11024
13376 ThreadState
12644 Launcher
9652 KotlinCompileDaemon
5032 Jps
  1. The process number corresponding to the example obtained from the previous step is 13376, and then input jstack 13376, you can get the thread stack of the current program and observe the status;
    part of the output is as follows:

// BlockedThread-2线程阻塞在获取Blocked.class示例的锁上
"BlockedThread-2" prio=5 tid=0x00007feacb05d000 nid=0x5d03 waiting for monitor
entry [0x000000010fd58000]
java.lang.Thread.State: BLOCKED (on object monitor)
// BlockedThread-1线程获取到了Blocked.class的锁
"BlockedThread-1" prio=5 tid=0x00007feacb05a000 nid=0x5b03 waiting on condition
[0x000000010fc55000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
// WaitingThread线程在Waiting实例上等待
"WaitingThread" prio=5 tid=0x00007feacb059800 nid=0x5903 in Object.wait()
[0x000000010fb52000]
java.lang.Thread.State: WAITING (on object monitor)
// TimeWaitingThread线程处于超时等待
"TimeWaitingThread" prio=5 tid=0x00007feacb058800 nid=0x5703 waiting on condition
[0x000000010fa4f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)

In its own life cycle, a thread is not fixed in a certain state, but switches between different states as the code is executed. Refer to the following figure for the state transition of Java threads:

Insert picture description here

Start and terminate threads

Before running a thread, we must first construct a thread object. The process object needs to provide the attributes required by the thread when it is constructed, such as the thread group to which the thread belongs, thread priority, whether it is a Daemon thread, and other information. For this part of the information, we can view the init() method of the Thread class. The following intercepts part java.lang.Threadof the initialization method init:

Insert picture description here

Start thread

After the thread object is initialized, call the start() method to start the thread. The meaning of the thread start() method is: the current thread (that is, the parent thread) synchronously informs the Java virtual machine that as long as the thread planner is idle, the thread calling the start() method should be started immediately.

注意:启动一个线程前,最好为这个线程设置线程名称,因为这样在使用jstack分析程序或者进行问题排查时,就会给我们提供一些提示,自定义的线程最好能够起个名字。

Safely terminate threads

Why is it said that the thread is safely terminated here, because the earliest tentative, resume, and stop threads use the Thread API: suspend(), resume(), stop(). However, these APIs have expired and are not recommended. The specific reasons why it is not recommended to use are mainly :
Take the suspend() method as an example. After the call, the thread will not release the occupied resources (such as locks), but occupy the resources and enter the sleep state, which is easy to cause deadlock problems. Similarly, the stop() method does not guarantee the normal release of the thread's resources when terminating a thread . Usually, the thread is not given the opportunity to complete the resource release work, so the program may work in an uncertain state.

The above only said do not recommend the use of threads to pause, resume, stop related API, then how to safely terminate the thread and were waiting for restoration does it?

  1. Use thread interruption, that is, call the thread's interrupt() method, change the interrupt state to realize the interaction between threads, thereby stopping the task;
  2. Use a boolean variable to control whether you need to stop the task to terminate the thread;

The sample code is as follows:

Insert picture description here

In the example, the main thread can stop CountThread through interrupt operation and cancel() method. Can these two methods give the thread a chance to clean up resources when it terminates, instead of arbitrarily stopping the thread, so this kind of termination thread The approach is more elegant and safe.

Smart you may ask, the problem of thread termination is solved, how about waiting and restoring. . .

Insert picture description here

The powerful Java definitely has a solution. We can use the wait/notify mechanism for specific thread waiting and recovery. Then look down, this will be mentioned in the explanation of the inter-thread communication mechanism.

Inter-thread communication

The thread starts to run and has its own stack space, just like a script, executing step by step according to the established code until it terminates. However, if each running thread runs in isolation, there is no value, or very little value. If multiple threads can cooperate with each other to complete the work, this will bring huge value.

Since the threads need to cooperate with each other to complete the work, the communication between threads is the first problem to be solved. Now let's learn what are the ways of communication between threads?

Volatile and synchronized keywords

In the previous articles on the Java memory model, we have deeply studied the volatile and synchronized keywords, which are provided at the jvm level to solve the three sources of concurrency bugs (visibility, orderliness, and atomicity).

We all know that Java supports multiple threads to access an object or member variables of an object at the same time, so it supports communication between threads. However, due to modern multi-core processors, in order to solve the performance difference between cpu and memory, each thread has a copy of the variable stored in the cpu cache, so during the execution of the program, what a thread sees may not be the latest The content of this affects the communication between threads.

The keyword volatile can be used to modify the field, which is to inform the program that access to the variable needs to be obtained from shared memory, and changes to it must be refreshed back to shared memory synchronously (the implementation principle is through a lock prefix instruction, which is equivalent to a memory barrier Its function is to immediately write the data of the current processor cache line back to the memory. The write-back operation spreads the data through the bus. Other processors sniff the data spread on the bus and find that the data change corresponding to the memory address will invalidate their respective caches. ) To ensure the visibility of all threads to variable access.

The keyword synchronized can be used to modify the method or in the form of a synchronized block. It mainly ensures that multiple threads can only be in the method or synchronized block at the same time. It ensures the visibility and visibility of thread access to variables. Exclusivity.

Waiting/notification mechanism

To put it simply, the wait/notify mechanism is the producer and consumer model. One thread modifies the value of an object, and the other thread perceives the change, and then executes the corresponding operation. So how to realize this function?

The simplest solution is to write an endless loop in the consumer thread to continuously check whether the variable meets expectations. You can refer to the following pseudo code:

while (value != desire) {
    
    
Thread.sleep(1000);
}
doSomething();

Although this method achieves the required functions, it does have the following problems:

  1. Not timely enough. Basically do not consume cpu resources during sleep, but sleep for too long, unable to find changes in time;
  2. It is difficult to reduce overhead. If you want to ensure timeliness, you must reduce the sleep time, which will consume more CPU resources;

You can find that the above two questions are mutually exclusive. Is there no more reasonable solution? This requires Java's built-in waiting notification mechanism to resolve this contradiction.

Wait / notification related to any Java object is included, because these methods are all objects defined in the originator java.lang.Object, the related method as follows:

Method name description
notify() Notify a thread waiting on the object to return from the wait() method, and the premise of the return is that the thread acquires the lock of the object
notifyAll() Notify all threads waiting on the object to change from WAITING state to BLOCKED state
wait() The thread that calls this method enters the WAITING state, and will only return when it is interrupted or notified by another thread. Note that calling the wait() method will release the object's lock
wait(long) Wait for a period of time over time, the unit is milliseconds, and return without notice after expiration
wait(long,int) For more fine-grained control of the timeout period, nanoseconds can be achieved

The waiting/notification mechanism means that a thread A calls the wait() method of object O to enter the waiting state, and another thread B calls the notify() or notifyAll() method of object O, and thread A receives the notification from the object O's wait() method returns, and then performs subsequent operations. The above two threads complete the interaction through the object O, and the relationship between wait() and notify/notifyAll() on the object is like a switch signal to complete the interaction between the waiting party and the notifying party.

There is also a corresponding sample code, due to space reasons, do not show the code, and if you want hands-on practice deepen understanding, the same, the examples in this article have been involved uploaded to github, click on the text to read the original to see the end, look for WaitNotifythis class.

There are several details to be mastered when using wait(), notify(), notifyAll():

  1. Call wait(), notify(), notifyAll(), you need to lock the calling object first;
  2. After calling the wait() method, the thread status changes from RUNNING to WAITING, releases the current object lock, and puts the current thread into the waiting queue of the object;
  3. After the notify() and notifyAll() methods are called, the waiting thread will still not return from wait(). After the thread that calls notify() and notifyAll() releases the lock, the waiting thread can compete for the lock, and the lock can be obtained from wait() returns;
  4. The notify() method moves a waiting thread in the waiting queue from the waiting queue to the synchronization queue, while the notifyAll() method moves all the threads in the waiting queue to the synchronization queue, and the state of the moved thread is changed from WAITING BLOCKED.
  5. The premise of returning from the wait() method is to obtain the lock of the calling object.

Pipeline input/output stream

The difference between pipeline input/output stream and ordinary file input/output stream or network input/output stream is that it is mainly used for data transmission between threads, and the transmission medium is memory.

There are four main implementations as follows: PipedOutputStream, PipedInputStream, PipedReader and PipedWriter, the first two are byte-oriented, and the latter two are character-oriented;

The usage is as follows:

PipedWriter out = new PipedWriter();
PipedReader in = new PipedReader();
// 将输出流和输入流进行连接,否则在使用时会抛出IOException
out.connect(in);

Thread.join()

If a thread A calls the join() method of thread B, then the current thread A waits for the termination of thread B before returning from B.join(). In addition to the join() method, the thread Thread also provides the wait timeout methods join(long) and join(long,int).

In fact, the wait/notification mechanism is also used here, that is, the A thread synchronously calls the join() method of the B thread, enters the loop waiting, and waits until the B thread ends, receives the notification and returns from the B.join() method, and executes the subsequent logic;

The following is the core source code of the Thread.join() method in the JDK, which means the same as what we described above (after partial adjustment):


// 加锁当前线程对象
public final synchronized void join(long millis)throws InterruptedException {
    
        
    // 条件不满足,继续等待
    while (isAlive()) {
    
               
        wait(0);        
     }    
     // 条件满足,返回
} 

Use of ThreadLocal

That ThreadLocal thread variable, based on the current thread as a parameter to get the current thread ThreadLocal.ThreadLocalMapproperty that ThreadLocalMapis bound to the current thread. This ThreadLocalMap is again a map data structure with a ThreadLocal object as the key and any object as the value. In other words, a thread can query a value bound to this thread based on a ThreadLocal object.

Application practice

As the saying goes, learning without thinking is equal to learning in vain. Next, we will combine the thread basics we learned today, including thread status and waiting for notifications and other thread-based methods, to implement a simple thread pool to consolidate and improve.

The main function is to create a certain number of threads in advance, and the user does not need to directly control the creation of threads. The user only needs to submit the tasks that need to be executed to the thread pool, and the thread pool reuses a fixed number of threads to complete the task. The advantage is to reduce the overhead of frequent creation and destruction of threads, avoiding one task and one thread from causing frequent context switching of the system, which increases the load of the system.

The thread pool interface is defined as follows:

Insert picture description here

The specific implementation is not shown here. If you are interested, you can go to my github to find the corresponding implementation.

to sum up

This article mainly expounds the basic knowledge of concurrent programming in Java, starting with what a thread is, describing the various states of the thread, and how to open and terminate gracefully and safely. Various communication methods between threads studied in detail, as well as the classic paradigm of waiting / notification mechanism, the last example by a thread pool, to consolidate the basics of Java multi-threaded, to deepen understanding.

I believe that if you can run the corresponding code examples as described in the article, you will have a deeper understanding and mastery of the basic knowledge of Java multithreading.

2020.04.12
fighting!

Personal public account

Insert picture description here

  • Friends who feel that they are writing well can bother to like and follow ;
  • If the article is incorrect, please point it out, thank you very much for reading;
  • I recommend everyone to pay attention to my official account, and I will regularly push original dry goods articles for you, and pull you into the high-quality learning community;
  • github address: github.com/coderluojust/qige_blogs

Guess you like

Origin blog.csdn.net/taurus_7c/article/details/105467897