Introduction:
Thread is the smallest unit of operating system scheduling. In a multi-core environment, multiple threads can execute at the same time. If used properly, it can significantly improve the performance of the program.
1, Preliminary understanding of threads
1. What is a thread
The operating system runs a program and starts a process for it. For example, starting a Java program creates a Java process. The smallest unit of modern operating system scheduling is thread, which is also called Light Weight Process. One or more threads can be created in a process. Threads have their own attributes such as counter, stack and local variables, and can access shared internal variables. The processor executes the program by quickly switching these threads.
2. Java itself is multithreaded
Example code:
package com.lizba.p2; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; import java.lang.management.ThreadMXBean; import java.util.Arrays; /** * <p> * * </p> * * @Author: Liziba * @Date: 2021/6/13 23:03 */ public class MultiThread { public static void main(String[] args) { // Get Java thread management MXBean ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); // Obtain thread and thread stack information; // boolean lockedMonitors = false. It is not necessary to obtain the synchronized monitor information; // boolean lockedSynchronizers = false. It is not necessary to obtain the synchronizer information of synchronization ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false); // Print thread ID and thread name Arrays.stream(threadInfos).forEach(threadInfo -> { System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName()); }); } } Copy code
Output results (not necessarily consistent):
[6] Monitor Ctrl break / / unique thread in idea (not managed)
[5] Attach listener / / communication thread between JVM processes
[4]Signal Dispatcher / / the thread that distributes signals sent to the JVM
[3]Finalizer / / the finalizer thread of the calling object
[2]Reference Handler / / clear the thread of reference
[1]main // main thread, user program entry
Summary:
From the output results, it is not difficult to see that the Java program itself is multi-threaded. It is not only one main thread running, but the main thread and multiple other threads running at the same time.
3. Why use multithreading
The benefits of using multithreading are as follows:
- More processor cores
The number of computer processor cores has increased, and the development from high dominant frequency to multi-core technology. Now computers are better at parallel computing. Therefore, how to make full use of multi-core processors is the main problem. Thread is the smallest unit of operating system scheduling. A program runs as a process. It will create multiple threads, and a thread can only run on one processor at the same time. Therefore, if a process can use multithreaded computing and allocate its computing logic to multiple processor cores, it will have a more significant performance improvement than single thread running.
- Faster response time
In complex business scenarios, we can dispatch non strongly consistent business to other threads (or use message queues). This can reduce the time for the application to respond to user requests
- Better programming model
Rational use of the multithreaded programming model provided by Java can make programmers better solve problems without too complicated consideration of how to multithread them.
4. Thread priority
The modern operating system basically adopts the method of time slice allocation to schedule threads, that is, the operating system divides the CPU operation into time slices, and the threads will allocate several time slices. When the thread time slices are used up, thread scheduling will occur and wait for the allocation of the next time slice. How long a thread can execute in a CPU scheduling depends on the number of time slices, and thread priority is the thread attribute that determines whether a thread needs to allocate more or less processor resources.
In Java threads, the settable range of thread priority is 1-10, and the default priority is 5. Theoretically, the number of time slices allocated by threads with high priority should take precedence over threads with low priority (this setting does not take effect in some operating systems);
Example code:
package com.lizba.p2; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; /** * <p> * Thread priority setting * </p> * * @Author: Liziba * @Date: 2021/6/14 12:03 */ public class Priority { /** Thread execution flow control switch */ private static volatile boolean notStart = true; /** Thread execution flow control switch */ private static volatile boolean notEnd = true; public static void main(String[] args) throws InterruptedException { List<Job> jobs = new ArrayList<>(); // Set 5 threads with priority 1 and 5 threads with priority 10 for (int i = 0; i < 10; i++) { int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY; Job job = new Job(priority); jobs.add(job); Thread thread = new Thread(job, "Thread:" + i); thread.setPriority(priority); thread.start(); } notStart = false; TimeUnit.SECONDS.sleep(10); notEnd = false; jobs.forEach( job -> System.out.println("Job priority : " + job.priority + ", Count : " + job.jobCount) ); } /** * Record the execution times and priority of threads through Job */ static class Job implements Runnable { private int priority; private long jobCount; public Job(int priority) { this.priority = priority; } @Override public void run() { while (notStart) { // Give up the CPU time slice and wait for the next scheduling Thread.yield(); } while (notEnd) { // Give up the CPU time slice and wait for the next scheduling Thread.yield(); jobCount++; } } } } Copy code
Execution results:
From the output results, the execution times of the thread with priority 1 and the thread with priority 10 are very similar, so it shows that the correctness of the program cannot depend on the priority of the thread.
\
5. Status of the thread
The life cycle of a thread is as follows:
Status name | explain |
---|---|
NEW | In the initial state, the thread is built and the start() method is not called |
RUNNABLE | Running state: Java threads collectively refer to the ready and running states in the operating system as "running" |
BLOCKED | Blocking state, thread blocking on lock |
WAITING | Wait state: the thread enters the wait state. Entering this state means that the current thread needs to wait for other threads to make some specific actions (notifications or interrupts) |
TIME_WAITING | Wait for a timeout. Wait first. You can return by yourself within the specified time |
TERMINATED | Termination status indicates that the current thread has completed execution |
View the status of Java threads through code
Code example:
package com.lizba.p2; import java.util.concurrent.TimeUnit; /** * <p> * Sleep time assignment tool class * </p> * * @Author: Liziba * @Date: 2021/6/14 13:27 */ public class SleepUtil { public static final void sleepSecond(long seconds) { try { TimeUnit.SECONDS.sleep(seconds); } catch (InterruptedException e) { e.printStackTrace(); } } } Copy code
package com.lizba.p2; /** * <p> * Thread status example code * </p> * * @Author: Liziba * @Date: 2021/6/14 13:25 */ public class ThreadStateDemo { public static void main(String[] args) { // TimeWaiting new Thread(new TimeWaiting(), "TimeWaitingThread").start(); // Waiting new Thread(new Waiting(), "WaitingThread").start(); // Blocked1 and Blocked2 succeeded in obtaining locks and failed in obtaining locks new Thread(new Blocked(), "Blocked1Thread").start(); new Thread(new Blocked(), "Blocked2Thread").start(); } // The thread keeps sleeping static class TimeWaiting implements Runnable { @Override public void run() { while (true) { SleepUtil.sleepSecond(100); } } } // The thread is waiting on the Waiting.class instance static class Waiting implements Runnable { @Override public void run() { while (true) { synchronized (Waiting.class) { try { Waiting.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } // The thread locks the Blocked.class instance and will not release the lock static class Blocked implements Runnable { @Override public void run() { synchronized (Blocked.class) { while (true) { SleepUtil.sleepSecond(100); } } } } } Copy code
To view Java processes using JPS:
To view the sample code, the ThreadStateDemo process ID is 2576. Type jstack 2576 to view the output:
Collate output results:
Thread name | Thread state |
---|---|
Blocked2Thread | BLOCKED (on object monitor), which is blocked on the lock of Blocked.class |
Blocked1Thread | TIMED_WAITING (sleeping) |
WaitingThread | WAITING (on object monitor) |
TimeWaitingThread | TIMED_WAITING (sleeping) |
Summary:
Threads are not specified to be in a certain state in their own life cycle, but switch between different states with the execution of code.
The state change diagram of Java thread is as follows:
Java thread state transition diagram
Summary:
- After the thread is created, the start() method is invoked to start running.
- After the thread executes the wait() method, the thread enters the waiting state. The waiting thread needs to rely on other threads to return to the running state
- Timeout waiting is equivalent to adding the timeout limit on the basis of waiting for Miss Zhu, and returning to the running state after reaching the set timeout
- When a thread executes a synchronization method or code block, the thread that does not obtain the lock will enter the blocking state.
- The thread enters the termination state after executing the run() method of Runnable
- The thread blocking the Lock interface in the concurrent package of Java is in a waiting state, because the implementation of the Lock interface blocking uses the Daemon thread
6. Daemon thread
Introduction:
Daemon thread is a kind of supporting thread. Its main function is background scheduling and supporting work in the program. When there is no non daemon thread in a Java virtual machine, the Java virtual machine will exit. Daemon thread needs to be set before startup and cannot be set after startup.
Setting method:
Thread.setDaemon(true) Copy code
Points needing special attention:
The Daemon thread is used as the completion of supporting work, but the finally code block of the Daemon thread does not necessarily execute when the Java virtual machine exits.
Example code:
package com.lizba.p2; /** * <p> * DaemonRunner thread * </p> * * @Author: Liziba * @Date: 2021/6/14 19:50 */ public class DaemonRunner implements Runnable{ @Override public void run() { try { SleepUtil.sleepSecond(100); } finally { System.out.println("DaemonRunner finally run ..."); } } } Copy code
Test:
package com.lizba.p2; /** * <p> * * </p> * * @Author: Liziba * @Date: 2021/6/14 19:59 */ public class DaemonTest { public static void main(String[] args) { Thread t = new Thread(new DaemonRunner(), "DaemonRunner"); t.setDaemon(true); t.start(); } } Copy code
Output results:
Summary:
It is not difficult to find that the finally code block of the run method of the DaemonRunner is not executed. This is because when there are no non daemon threads in the Java virtual machine, the virtual opportunity exits immediately. The daemon threads in the virtual machine need to be terminated immediately, so the thread DaemonRunner will be terminated immediately and finally not executed.
2, Thread startup and termination
1. Construction thread
Before running a thread, you need to construct a thread object. When constructing a thread object, you need to set some thread properties, including thread group, thread priority, daemon thread, thread name and so on.
Code example:
From java.lang.Thread
/** * Initializes a Thread. * * @param g the Thread group * @param target the object whose run() method gets called * @param name the name of the new Thread * @param stackSize the desired stack size for the new thread, or * zero to indicate that this parameter is to be ignored. * @param acc the AccessControlContext to inherit, or * AccessController.getContext() if null * @param inheritThreadLocals if {@code true}, inherit initial values for * inheritable thread-locals from the constructing thread */ private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } // Set thread name this.name = name; // The current thread is set as the parent thread of this thread Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { if (security != null) { g = security.getThreadGroup(); } if (g == null) { g = parent.getThreadGroup(); } } g.checkAccess(); if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); // Set thread group this.group = g; // Set the daemon property to the corresponding property of the parent thread this.daemon = parent.isDaemon(); // Set the priority property to the corresponding property of the parent thread this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); this.target = target; setPriority(priority); // Copy the InheritableThreadLocals property of the parent thread if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; // Set a thread id tid = nextThreadID(); } Copy code
Summary:
In the above code, a newly constructed thread object is allocated space by its parent thread, and child inherits whether the parent is Daemon, priority, contextClassLoader for loading resources and inheritable ThreadLocal. At the same time, it will assign a unique ID to mark the thread. At this point, a complete thread object that can run is initialized and waiting to run in heap memory.
2. What is a thread interrupt
Interrupt can be understood as an identification bit attribute of a thread, which indicates whether a running thread has been interrupted by other threads. The thread responds by checking whether it is interrupted. The thread judges whether it is interrupted through the method isInterrupted(). It can also reset the interrupt flag bit of the current thread by calling the static method Thread.interrupted().
The following situations cannot accurately determine whether the thread has been interrupted:
- The thread has terminated. Even if it is interrupted, the isInterrupted() method will return false
- Method throws an InterruptedException exception. Even if it is interrupted, calling the isInterrupted() method will return false because the interrupt flag will be cleared before throwing an InterruptedException.
Example code:
package com.lizba.p2; /** * <p> * Thread interrupt example code * </p> * * @Author: Liziba * @Date: 2021/6/14 20:36 */ public class Interrupted { public static void main(String[] args) { // sleepThread keeps trying to sleep Thread sleepThread = new Thread(new SleepRunner(), "sleepThread"); sleepThread.setDaemon(true); // busyThread Thread busyThread = new Thread(new BusyRunner(), "busyThread"); busyThread.setDaemon(true); // Start two threads sleepThread.start(); busyThread.start(); // Sleep for 5 seconds and let sleepThread and busyThread run fully SleepUtil.sleepSecond(5); // Interrupt two threads sleepThread.interrupt(); busyThread.interrupt(); System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted()); System.out.println("BusyThread interrupted is " + busyThread.isInterrupted()); // Sleep the main thread to prevent the daemon thread from exiting SleepUtil.sleepSecond(2); } static class SleepRunner implements Runnable { @Override public void run() { while (true) { SleepUtil.sleepSecond(10); } } } static class BusyRunner implements Runnable { @Override public void run() { while (true) {} } } } Copy code
View run results:
Summary:
The sleepThread thread thread throws the InterruptedException. Although both threads have been interrupted, the interrupt flag of the sleepThread thread returns false because TimeUnit.SECONDS.sleep(seconds) will throw the InterruptedException exception. Before throwing the exception, the interrupt flag of the sleepThread thread is cleared. However, busyThread has been running without throwing an exception, and the interrupt bit has not been cleared.
3. suspend(), resume(), and stop()
give an example:
Thread these three methods are equivalent to the pause, resume and stop operations of QQ music when playing music. (note that these methods have expired and are not recommended.)
Example code:
package com.lizba.p2; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.concurrent.TimeUnit; /** * <p> * Thread expiration method example * </p> * * @Author: Liziba * @Date: 2021/6/14 20:57 */ public class Deprecated { static DateFormat format = new SimpleDateFormat("HH:mm:ss"); public static void main(String[] args) { Thread printThread = new Thread(new PrintThread(), "PrintThread"); printThread.start(); SleepUtil.sleepSecond(3); // Pause printThread output printThread.suspend(); System.out.println("main suspend PrintThread at " + format.format(new Date())); SleepUtil.sleepSecond(3); // Restore printThread output printThread.resume(); System.out.println("main resume PrintThread at " + format.format(new Date())); SleepUtil.sleepSecond(3); // Terminate printThread output printThread.stop(); System.out.println("main stop PrintThread at " + format.format(new Date())); SleepUtil.sleepSecond(3); } static class PrintThread implements Runnable { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName() + "Run at " + format.format(new Date())); SleepUtil.sleepSecond(1); } } } } Copy code
Output results:
Summary:
The results of the above code execution output are consistent with the API description and our expected completion, but the seemingly correct code hides many problems.
Problems:
- After the suspend() method is called, the occupied resources (such as locks) will not be released, which may lead to deadlock
- The stop() method cannot guarantee the normal release of resources when terminating a thread, which may cause the program to be in an uncertain working state
4. Correct thread termination
- Call the interrupt() method of the thread
- A Boolean variable is used to control whether to stop the task and terminate the thread
Example code:
package com.lizba.p2; /** * <p> * Flag bit termination thread example code * </p> * * @Author: Liziba * @Date: 2021/6/14 21:17 */ public class ShutDown { public static void main(String[] args) { Runner one = new Runner(); Thread t = new Thread(one, "CountThread"); t.start(); SleepUtil.sleepSecond(1); t.interrupt(); Runner two = new Runner(); t = new Thread(two, "CountThread"); t.start(); SleepUtil.sleepSecond(1); two.cancel(); } private static class Runner implements Runnable { private long i; private volatile boolean on = true; @Override public void run() { while (on && !Thread.currentThread().isInterrupted()) { i++; } System.out.println("Count i = " +i); } /** * close */ public void cancel() { on = false; } } } Copy code
Output results:
Summary:
The main thread can terminate the CountThread through the interrupt operation and the cancel() method. The advantage of these two methods is that it gives the thread the opportunity to clean up resources when it terminates. It is safer and more elegant.
Author: Li Ziba
Link: https://juejin.cn/post/7018350144785481736
Source: rare earth Nuggets