Processes and threads
process
The program consists of instructions and data, but to run these instructions and read and write the data, you must load the instructions to the CPU and the data to the memory. Disk, network and other equipment are also needed in the process of instruction operation. Processes are used to load instructions, manage memory and manage IO
When a program is run and the code of the program is loaded from disk to memory, a process is started.
The process can be regarded as an instance of the program. Most programs can run multiple instance processes at the same time (such as Notepad, drawing, browser, etc.), while some programs can only start one instance process (such as Netease cloud music, 360 security guard, etc.)
thread
A process can be divided into one or more threads.
A thread is an instruction stream, which gives instructions in the instruction stream to the CPU for execution in a certain order
In Java, thread is the smallest scheduling unit and process is the smallest unit of resource allocation. In windows, the process is inactive, just as a container for threads
Multiple threads in the same process can share code segments, data segments, open files and other resources, but each thread has a set of unique registers and stacks, which can ensure that the thread control flow is relatively unique.
contrast
- Processes are basically independent of each other, and threads exist in processes and are a subset of processes
- A process has shared resources, such as memory space, for its internal threads to share
- Inter process communication is complex
- The process communication of the same computer is called IPC (inter process communication)
- Process communication between different computers needs to pass through the network and abide by common protocols, such as HTTP
- Thread communication is relatively simple because they share memory within the process. An example is that multiple threads can access the same shared variable
- Threads are lighter, and the cost of thread context switching is generally lower than that of process context switching
Parallelism and concurrency
Under single core cpu, threads are actually executed serially. There is a component in the operating system called task scheduler, which distributes the cpu time slice (the minimum time slice under windows is about 15ms) to different programs, but because the cpu switches between processes very quickly (the time slice is very short), human beings feel that it runs at the same time,
Generally, this method of using CPU in turn by threads is called concurrency
Under multi-core cpu, each core can schedule running threads. At this time, threads can be parallel.
JMH environment construction
JMH, namely Java Microbenchmark Harness, is a tool suite specially used for code microbenchmark testing. It will perform program preheating, perform multiple tests and average
In short, it is a benchmark based on the method level, and the accuracy can reach the microsecond level. When you locate a hot method and want to further optimize the performance of the method, you can use JMH to quantitatively analyze the optimization results.
Typical application scenarios of JMH include:
- Want to know exactly how long a method needs to be executed and the correlation between execution time and input;
- Compare the throughput of different interface implementations under given conditions to find the optimal implementation
- See what percentage of requests are completed and how long
Generate project template
mvn archetype:generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=com.javaming.study -DartifactId=springboot-concurrent -Dversion=0.0.1-SNAPSHOT
View process / thread information
ps -ef | grep redis # View redis process ps -fT -p <PID> # View all threads of a process (PID)
top # Press 1 to display each CPU information, and then press to switch back to the CPU information after statistics
top -H -p <PID> # View all threads of a process (PID)
Thread running principle
Stack and stack frame
The JVM consists of heap, stack and method area. Who is the stack memory for? In fact, it is a thread. After each thread is started, the virtual machine will allocate a stack memory for it.
Each stack consists of multiple stack frames, corresponding to the memory occupied by each method call
Each thread can only have one active stack frame, corresponding to the method currently executing
Thread Context Switch
For the following reasons, the cpu no longer executes the current thread, but executes the code of another thread
- The thread has run out of cpu time slices
- garbage collection
- Threads with higher priority need to run
- The thread calls sleep, yield, wait, join, park, synchronized, lock and other methods
When a Context Switch occurs, the operating system needs to save the state of the current thread and restore the state of another thread. The corresponding concept in Java is the Program Counter Register, which is used to remember the execution address of the next jvm instruction and is private to the line
- Status includes program counter, information of each stack frame in virtual machine stack, such as local variable, operand stack, return address, etc
- Frequent occurrence of context switches will affect performance
Thread common methods
Sleep
- Calling sleep will make the current thread enter the Timed Waiting state (blocking) from Running
- Other threads can use the interrupt method to interrupt the sleeping thread, and the sleep method will throw an InterruptedException
- The thread after sleep may not be executed immediately
yield
- Calling yield will enable the current thread to enter the Runnable ready state from Running, and then schedule other threads.
- The specific implementation depends on the task scheduler of the operating system
join
Add t1.0 to the main thread After joining (), you must wait for T1 execution to finish before continuing
If T1 Join (1000) specifies to wait for 1 second, then j1 will print = 0 after 1 second
If T1 Join (3000) specifies to wait for 3 seconds, then j1 will print = 10 after 2 seconds
static int j1 = 0; public static void main(String[] args) throws InterruptedException { log.debug("Start running"); Thread t1 = new Thread(() -> { try { sleep(2000); j1 = 10; } catch (InterruptedException e) { e.printStackTrace(); } }, "t1"); t1.start(); //Wait for the thread to finish running t1.join(1000); log.debug("End operation j1=" + j1); }
interrupt
private static void testInterrupt() throws InterruptedException { Thread t1 = new Thread(() -> { try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { // Interrupting a dormant thread clears the interrupt state log.error("Interrupted isInterrupt = " + Thread.currentThread().isInterrupted()); } }, "t1"); t1.start(); Thread t2 = new Thread(() -> { while (true) { // Interrupting a running thread does not clear the interrupt state boolean interrupted = Thread.currentThread().isInterrupted(); if(interrupted) { log.error("Interrupted isInterrupt = " + Thread.currentThread().isInterrupted()); break; } } }, "t2"); t2.start(); TimeUnit.SECONDS.sleep(1); log.debug("interrupt..."); t1.interrupt(); t2.interrupt(); }
interrupted
Like the interrupt method, it determines whether the current thread is interrupted, but this method is a static method, and the interrupt flag will be cleared when calling
By viewing the print results, you can know:
- Interrupting a dormant thread clears the interrupt state
- Interrupting a running thread does not clear the interrupt state
Two stage termination mode
How to gracefully stop thread t2 in a thread t1?
If you use the stop() method: the stop() method will really kill the thread. If the thread locks the shared resource, it will never have a chance to release the lock after it is killed, and other threads will never get the lock
At this time, it depends on the two-phase termination mode to stop the thread
public class TestStopThread { private Thread monitorThread; /** * Start monitoring thread */ public void start(){ monitorThread = new Thread(() -> { while (true) { boolean interrupted = Thread.currentThread().isInterrupted(); if(interrupted) { log.debug("Deal with the aftermath"); break; } try { TimeUnit.SECONDS.sleep(2); log.debug("Perform monitoring"); } catch (InterruptedException e) { } } }, "monitor"); monitorThread.start(); } /** * Stop monitoring thread */ public void stop(){ monitorThread.interrupt(); } }
interrupt on locksupport Park() effect
public static void main(String[] args) { Thread t1 = new Thread(() -> { log.debug("park...."); LockSupport.park(); log.debug("unpark..."); log.debug("Interrupt state = " + Thread.currentThread().isInterrupted()); LockSupport.park(); log.debug("unpark..."); }, "t1"); t1.start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t1.interrupt(); }
You can see that even if you restart locksupport It cannot take effect after park(), because park can continue to take effect only when the interrupt is marked as false
Interrupt when breaking the lock of a park, the interrupt flag is set to true. Even if you re Park, it will not take effect
// log.debug("interrupt status =" + thread. Currentthread() isInterrupted()); log.debug("Interrupt state = " + Thread.interrupted());
After changing to the above statement, the thread can be locked normally
Daemon thread
By default, the Java process will not end until all threads have finished running. A special thread is called a daemon thread. As long as other non daemon threads finish running, even if the code of the daemon thread is not executed, it will be forced to end.
be careful
The garbage collector thread is a daemon thread
Acceptor and Poller threads in Tomcat are daemon threads, so Tomcat will not wait for them to process the current request after receiving the shutdown command