1. Processes and threads
1. What is the process?
Narrow definition: a process is an instance of a computer program that is being executed.
General definition: A process is a running activity of a program with certain independent functions regarding a certain data set. It is the basic unit of dynamic execution of the operating system. In traditional operating systems, processes are both basic allocation units and basic execution units.
2. What is a thread?
Threads, sometimes called lightweight processes (LWP), are the smallest units of program execution flow. A standard thread consists of a thread ID, current instruction pointer (PC), a set of registers, and a stack. In addition, a thread is an entity in the process and is the basic unit that is independently scheduled and dispatched by the system. The thread itself does not own the system resources, but only has a few essential resources during operation, but it can share all the resources owned by the process with other threads belonging to the same process.
3. What is the difference between a process and a thread?
The main difference between processes and threads is that they are different operating system resource management methods.
A process has an independent address space. After a process crashes, it will not affect other processes in protected mode, and a thread is just a different execution path in a process.
Threads have their own stack and local variables, but there is no separate address space between threads. If a thread dies, it means that the entire process dies. Therefore, multi-process programs are more robust than multi-thread programs, but when switching processes, they consume more resources and are less efficient. However, for some concurrent operations that require concurrent operations that require sharing certain variables, they can only use threads, not processes.
In short, the difference between a thread and a process is:
(1) A program has at least one process, and a process has at least one thread;
(2) The division scale of threads is smaller than that of process, making the concurrency of multi-threaded programs high.
(3) The process has independent memory units during execution, and multiple threads share memory, which greatly improves the operation efficiency of the program.
(4) There is a difference between threads and processes during execution. Each independent thread has an entry for program execution, a sequence of execution and an exit for program. However, threads cannot be executed independently, and must exist in the application, and multiple thread execution controls are provided by the application.
(5) From a logical point of view, the meaning of multi-threading lies in that in an application, multiple execution parts can be executed at the same time. However, the operating system does not regard multiple threads as multiple independent applications to realize process scheduling, management and resource allocation.
This is the important difference between a process and a thread.
2. The life cycle of a thread and the five basic states
Java threads have five basic states:
(1) New state (New): When the thread object pair is created, it enters the new state, such as: Thread t = new MyThread();
(2) Ready state (Runnable): When the start() method of the thread object (t.start();), the thread enters the ready state. A thread in the ready state just means that the thread is ready and is waiting for the CPU to schedule execution at any time, not that the thread will execute immediately after t.start() is executed;
(3) Running state: When the CPU starts to schedule threads in the ready state, the thread can be truly executed, that is, it enters the running state. Note: The ready state is the only entry to the running state, that is, if a thread wants to enter the running state to execute, it must first be in the ready state;
(4) Blocked state: For some reason, a thread in the running state temporarily gives up its use of the CPU and stops execution. At this time, it enters the blocking state. It will not have the chance to be called by the CPU again to enter the running state. According to the reasons for blocking, blocking states can be divided into three types:
① Waiting for blocking: The thread in the running state executes the wait() method to make the thread enter the waiting for blocking state;
② Synchronized blocking: The thread fails to acquire the synchronized synchronization lock (because the lock is occupied by other threads), and it will enter the synchronized blocking state;
③Other blocking: When calling the thread's sleep() or join() or sending an I/O request, the thread will enter a blocking state. When the sleep() state timed out, join() waited for thread to terminate or timed out, or I/O processing was completed, the thread re-entered to the ready state.
(5) Dead state: The thread has finished executing or exited the run() method due to an exception, and the thread ends its life cycle.
3. Implementation of Java multithreading
In Java, if you want to implement a multi-threaded program, you must rely on the main class of a thread (like the concept of a main class, which represents the main class of a thread), but the main class of this thread needs to have some special requirements when defining it. This class can inherit the Thread class or implement the Runnable interface to complete the definition.
1. Inherit the Thread class to implement multi-threading
java.lang.Thread is a class responsible for thread operations. Any class can become the main class of a thread if it inherits the Thread class. Since it is the main class, it must have its usage methods, and the main method started by the thread needs to overwrite the run() method in the Thread class.
Define the body class of a thread:
class MyThread extends Thread { // The main class of the thread private String title; public MyThread(String title) { this.title = title; } @Override public void run() { // The main method of the thread for (int x = 0; x < 10; x++) { System.out.println(this.title + "Run, x = " + x); } }}Now that there is a thread class and there are corresponding operation methods in it, the object should be generated and the methods inside should be called, so the following program is written:
public class TestDemo { public static void main(String[] args) { MyThread mt1 = new MyThread("Thread A"); MyThread mt2 = new MyThread("Thread B"); MyThread mt3 = new MyThread("Thread C"); mt1.run(); mt2.run(); mt3.run(); }Running results:
Thread A runs, x = 0
Thread A runs, x = 1
Thread A runs, x = 2
Thread A runs, x = 3
Thread A runs, x = 4
Thread A runs, x = 5
Thread A runs, x = 6
Thread A runs, x = 7
Thread A runs, x = 8
Thread A runs, x = 9
Thread B runs, x = 0
Thread B runs, x = 1
Thread B runs, x = 2
Thread B runs, x = 3
Thread B runs, x = 4
Thread B runs, x = 5
Thread B runs, x = 6
Thread B runs, x = 7
Thread B runs, x = 8
Thread B runs, x = 9
Thread C runs, x = 0
Thread C runs, x = 1
Thread C runs, x = 2
Thread C runs, x = 3
Thread C runs, x = 4
Thread C runs, x = 5
Thread C runs, x = 6
Thread C runs, x = 7
Thread C runs, x = 8
Thread C runs, x = 9
We found that the above operations do not really start multi-threading, because the execution of multiple threads must be alternately run, and at this time it is executed sequentially, and the code of each object will continue to be executed downwards after the code of each object is executed.
If you want to truly start multi-threading in a program, you must rely on a method of the Thread class: public void start(), which means that you really start multi-threading. After calling this method, the run() method will be indirectly called:
public class TestDemo { public static void main(String[] args) { MyThread mt1 = new MyThread("Thread A"); MyThread mt2 = new MyThread("Thread B"); MyThread mt3 = new MyThread("Thread C"); mt1.start(); mt2.start(); mt3.start(); }}Running results:
Thread C runs, x = 0
Thread A runs, x = 0
Thread B runs, x = 0
Thread A runs, x = 1
Thread C runs, x = 1
Thread A runs, x = 2
Thread B runs, x = 1
Thread A runs, x = 3
Thread A runs, x = 4
Thread A runs, x = 5
Thread C runs, x = 2
Thread C runs, x = 3
Thread C runs, x = 4
Thread C runs, x = 5
Thread C runs, x = 6
Thread C runs, x = 7
Thread C runs, x = 8
Thread C runs, x = 9
Thread A runs, x = 6
Thread A runs, x = 7
Thread A runs, x = 8
Thread A runs, x = 9
Thread B runs, x = 2
Thread B runs, x = 3
Thread B runs, x = 4
Thread B runs, x = 5
Thread B runs, x = 6
Thread B runs, x = 7
Thread B runs, x = 8
Thread B runs, x = 9
At this time, you can find that multiple threads execute alternately with each other, but the results of each execution are different. Through the above code, we can draw a conclusion: if you want to start a thread, you must rely on the start() method of the Thread class to execute. After the thread starts, the run() method will be called by default.
After calling the start() method, a series of complicated things happened:
(1) Start a new execution thread (with a new call stack);
(2) The thread is transferred from the new state to the runnable state;
(3) When the thread gets the opportunity to execute, its target run() method will run.
Note: For Java, the run() method has nothing special. Like the main() method, it just means that the new thread knows the method name (and signature) of the call. Therefore, it is legal to call the run method on Runnable or Thread, but does not start a new thread.
Explanation: Why do you have to call start() instead of directly calling run() when a thread starts?
We found that after calling start(), it actually executes the overridden run() method, so why not call the run() method directly? To explain this problem, open the source code of the Thread class and observe the definition of the start() method:
public synchronized void start() { if (threadStatus != 0) throw new IllegalThreadStateException(); group.add(this); boolean started = false; try { start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { } } } private native void start0();Open the source code of this method and you will first find that the method will throw an "IllegalThreadStateException" exception. Generally speaking, if a method uses throw to throw an exception object, then this exception should be caught using try...catch, or thrown using throws on the method declaration, but there is nothing in this area. Why? Because this exception class belongs to a subclass of runtime exception (RuntimeException):
java.lang.Object
|- java.lang.Throwable
|- java.lang.Exception
|- java.lang.RuntimeException
|- java.lang.IllegalArgumentException
|- java.lang.IllegalThreadStateException
This exception will be thrown when a thread object is repeatedly started, that is, a thread object can only be started once.
One of the most critical parts of the start() method is the start0() method, and this method uses a native keyword definition.
The native keyword refers to Java Native Interface, that is, Java is used to call the function functions of the native operating system to complete some special operations. Such code development is almost rarely seen in Java because the biggest feature of Java is portability. If a program can only be used on a fixed operating system, portability will be completely lost, so this operation is generally not used.
The implementation of multithreading must require the support of the operating system. Then the above start0() method is actually very similar to the abstract method without a method body. This method body is handed over to the JVM to implement, that is, the JVM in Windows may use the A method to implement start0(), while the JVM in Linux may use the B method to implement start0(), but when calling, it will not care about the specific method of implementing the start0() method, but only care about the final operation result, and handed over to the JVM to match different operating systems.
Therefore, in multi-threaded operations, using the start() method to start multi-threaded operations requires operating system function calls.
2. Implement the Runnable interface to implement multi-threading
Using the Thread class can indeed facilitate multi-threading implementation, but the biggest disadvantage of this method is the problem of single inheritance. To this end, the Runnable interface can also be used in Java to implement multi-threading. The definition of this interface is as follows:
public interface Runnable { public void run();}Implement multi-threading through the Runnable interface:
class MyThread implements Runnable { // The thread's main class private String title; public MyThread(String title) { this.title = title; } @Override public void run() { // The thread's main method for (int x = 0; x < 10; x++) { System.out.println(this.title + "Run, x = " + x); } }}This is not much different from the previous way of inheriting Thread classes, but one advantage is that it avoids the limitation of single inheritance.
But the problem is here. As mentioned before, if you want to start multi-threading, you need to rely on the start() method of the Thread class. When you inherit the Thread class, you can directly inherit this method and use it. But now you are implementing the Runable interface. Without this method, you can inherit it. What should you do?
To solve this problem, you still need to rely on the Thread class to complete it. A constructor is defined in the Thread class to receive the Runnable interface object:
public Thread(Runnable target);
Start multithreading using Thread class:
public class TestDemo { public static void main(String[] args) throws Exception { MyThread mt1 = new MyThread("Thread A"); MyThread mt2 = new MyThread("Thread B"); MyThread mt3 = new MyThread("Thread C"); new Thread(mt1).start(); new Thread(mt2).start(); new Thread(mt3).start(); }}Running results:
Thread A runs, x = 0
Thread B runs, x = 0
Thread B runs, x = 1
Thread C runs, x = 0
Thread B runs, x = 2
Thread A runs, x = 1
Thread B runs, x = 3
Thread C runs, x = 1
Thread C runs, x = 2
Thread B runs, x = 4
Thread B runs, x = 5
Thread A runs, x = 2
Thread A runs, x = 3
Thread A runs, x = 4
Thread A runs, x = 5
Thread A runs, x = 6
Thread A runs, x = 7
Thread A runs, x = 8
Thread A runs, x = 9
Thread B runs, x = 6
Thread B runs, x = 7
Thread B runs, x = 8
Thread B runs, x = 9
Thread C runs, x = 3
Thread C runs, x = 4
Thread C runs, x = 5
Thread C runs, x = 6
Thread C runs, x = 7
Thread C runs, x = 8
Thread C runs, x = 9
At this time, not only multi-threaded startup is achieved, but also no single inheritance limitations.
3. The third method to implement multi-threading: Use the Callable interface to implement multi-threading
Multithreading using the Runnable interface can avoid the limitation of single inheritance, but there is a problem that the run() method in the Runnable interface cannot return the operation result. To solve this problem, a new interface is provided: the Callable interface (java.util.concurrent.Callable).
public interface Callable<V>{ public V call() throws Exception;}After executing the call() method in the Callable interface, a result will be returned. The type of result returned is determined by the generics on the Callable interface.
The specific operation of implementing the Callable interface to implement multi-threading is:
Create an implementation class of the Callable interface and implement the clall() method; then use the FutureTask class to wrap the object of the Callable implementation class, and use this FutureTask object as the target of the Thread object to create a thread.
Define a thread body class:
import java.util.concurrent.Callable;class MyThread implements Callable<String>{ private int ticket = 10; @Override public String call() throws Exception { for(int i = 0 ; i < 20 ; i++){ if(this.ticket > 0){ System.out.println("Sell tickets, the remaining number of votes is "+ this.ticket --); } } return "Tickets have been sold out"; }}The Thread class does not directly support the Callable interface. After JDK1.5, a class is provided:
java.util.concurrent.FutureTask<V>
This class is mainly responsible for the operation of Callable interface object. Its definition structure is as follows:
public class FutureTask<V>
extends Object
implements RunnableFurture<V>
The RunnableFurture interface has the following definition:
public interface RunnableFurture<V>
extends Runnable,Future<V>
The following constructor is defined in the FutureTask class:
public FutureTask(Callable<V> callable)
Now, you can finally receive the Callable interface object through the FutureTask class. The purpose of receiving is to obtain the return result of the call() method.
From the above analysis we can find:
The FutureTask class can receive Callable interface objects, and the FutureTask class implements the RunnableFurture interface, which inherits the Runnable interface.
So, we can start multithreading like this:
public class TestDemo { public static void main(String[] args) throws Exception { MyThread mt1 = new MyThread(); MyThread mt2 = new MyThread(); FutureTask<String> task1 = new FutureTask<String>(mt1);//Get call() method to return the result FutureTask<String> task2 = new FutureTask<String>(mt2);//Get call() method to return the result//FutureTask is a subclass of the Runnable interface. You can use the Thread class construction to receive task objects new Thread(task1).start(); new Thread(task2).start(); //After multithread execution is completed, you can use the get() method in FutureTask's parent interface Future to obtain the execution result System.out.println("Thread 1's return result: "+task1.get()); System.out.println("Thread 2's return result: "+task2.get()); }}Running results:
Sell tickets, the remaining number of votes is 10
Sell tickets, the remaining number of votes is 10
Sell tickets, the remaining number of votes is 9
Sell tickets, the remaining number of votes is 8
Sell tickets, the remaining number of votes is 7
Sell tickets, the remaining number of votes is 9
Sell tickets, the remaining number of votes is 6
Sell tickets, the remaining number of votes is 8
Sell tickets, the remaining number of votes is 5
Sell tickets, the remaining number of votes is 7
Sell tickets, the remaining number of votes is 4
Sell tickets, the remaining number of votes is 6
Sell tickets, the remaining number of votes is 3
Sell tickets, the remaining number of votes is 5
Sell tickets, the remaining number of votes is 2
Sell tickets, the remaining number of votes is 4
Sell tickets, the remaining number of votes is 1
Sell tickets, the remaining number of votes is 3
Sell tickets, the remaining number of votes is 2
Sell tickets, the remaining number of votes is 1
The return result of thread 1: The ticket has been sold out. The return result of thread 2: The ticket has been sold out.
summary:
The above explains three ways to implement multithreading. For thread startup, they are all called the start() method of the thread object. It is important to note that the start() method cannot be called twice on the same thread object.