Multithreading is a mechanism that allows concurrent execution of Multiple Instruction Streams in a program. Each instruction stream is called a thread and is independent of each other. A thread is also called a lightweight process. It has independent execution control like a process and is scheduled by the operating system. The difference is that the thread does not have independent storage space, instead, it shares a storage space with other threads in the process, which makes the communication between threads much simpler than the process.
I. multi-thread understanding
Multithreading is a mechanism that allows concurrent execution of Multiple Instruction Streams in a program. Each instruction stream is called a thread and is independent of each other. A thread is also called a lightweight process. It has independent execution control like a process and is scheduled by the operating system. The difference is that the thread does not have independent storage space, instead, it shares a storage space with other threads in the process, which makes the communication between threads much simpler than the process.
The execution of multiple threads is concurrent, that is, logically "at the same time", regardless of whether it is physically "at the same time ". If the system only has one CPU, the real "at the same time" is impossible, but because the CPU speed is very fast, users do not feel the difference, so we do not need to care about it, you only need to imagine that each thread is executed at the same time.
The biggest difference between multithreading and traditional single-Thread Programming is that the control flow of each thread is independent of each other, so that the code between each thread is executed in disorder, the thread scheduling and synchronization issues will be discussed later.
2. Implement multithreading in Java
What do we need to do to create a new thread? Obviously, we must specify the code to be executed by this thread, and this is all we need to do to implement multithreading in Java!
Amazing! How does Java achieve this? Passed class! As a fully object-oriented language, Java provides a class of Java. lang. thread to facilitate multi-threaded programming. This class provides a large number of methods to facilitate our control of various threads. We will discuss this class in the future.
So how can we provide the code to be executed by threads for Java? Let's take a look at the Thread class. The most important method of the thread class is run (), which is called by the Thread class method start () and provides the code to be executed by our thread. To specify our own code, we only need to overwrite it!
Method 1: Inherit the Thread class and overwrite the run () method. In the subclass of the created Thread class, rewrite run () and add the code to be executed by the thread. The following is an example:
Public class mythread extends thread {
Int COUNT = 1, number;
Public mythread (INT num ){
Number = num;
System. Out. println ("creation thread" + number );
}
Public void run (){
While (true ){
System. Out. println ("Thread" + number + ": Count" + count );
If (++ COUNT = 6) return;
}
}
Public static void main (string ARGs []) {
For (INT I = 0; I <5; I ++) New mythread (I + 1). Start ();
}
}
This method is simple and clear, and conforms to everyone's habits. However, it also has a major drawback, that is, if our class has been inherited from a class (for example, the applet must inherit from the Applet Class), we cannot inherit from the Thread class. If we do not want to create a new class, what should I do?
We may wish to explore a new method: instead of creating a subclass of the thread class, we can directly use it, so we can only pass our method as a parameter to the Thread class instance, similar to callback functions. But Java has no pointer. We can only pass an instance of the class containing this method. So how can we limit that this class must contain this method? Of course, the interface is used! (Although abstract classes can also be satisfied, but inheritance is required. Why should we use this new method to avoid the limitations of inheritance ?)
Java provides the java. Lang. runnable interface to support this method.
Method 2: implement the runnable interface
The runnable interface has only one method run (). We declare our class to implement the runnable interface and provide this method. By writing our thread code into it, this part of the task is completed. However, the runnable interface does not support any threads. We must also create an instance of the thread class, which is achieved through the Thread class constructor public thread (runnable target. The following is an example:
Public class mythread implements runnable {
Int COUNT = 1, number;
Public mythread (INT num ){
Number = num;
System. Out. println ("creation thread" + number );
}
Public void run (){
While (true ){
System. Out. println ("Thread" + number + ": Count" + count );
If (++ COUNT = 6) return;
}
}
Public static void main (string ARGs []) {
For (INT I = 0; I <5; I ++) New thread (New mythread (I + 1). Start ();
}
}
Strictly speaking, creating a thread subclass instance is also feasible, but it must be noted that this subclass must not overwrite the Thread class's run method, otherwise, the thread will execute the run method of the subclass, rather than the run method of the class we use to implement the runnable interface. You may try this out.
Using the runnable interface to implement multithreading enables us to include all the code in a class and facilitate encapsulation. Its disadvantage is that we can only use one set of code, if you want to create multiple threads and run different code in each thread, you must create additional classes, in most cases, it may be better to use multiple classes to inherit the thread.
In summary, the two methods have their own merits and can be used flexibly.
Next, let's take a look at some of the problems in multithreading.
3. Four States of a thread
1. New status: the thread has been created but not executed (START () has not been called ).
2. executable status: the thread can be executed, although not necessarily in progress. The CPU time may be allocated to this thread at any time for execution.
3. Death state: Normally, running () returns to cause the thread to die. Calling stop () or destroy () has the same effect, but is not recommended. The former produces exceptions, and the latter forces termination without releasing the lock.
4. Blocking status: the thread will not be allocated CPU time and cannot be executed.
4. thread priority
The thread priority indicates the importance of the thread. When multiple threads are in the executable status and wait for the CPU time to be obtained, the thread scheduling system determines to whom to allocate the CPU time based on the priority of each thread. A thread with a higher priority has a larger chance to obtain the CPU time. A thread with a lower priority is not without a chance, it's just a little opportunity.
You can call the methods of the thread class getpriority () and setpriority () to access the priority of the thread. The priority of the thread is between 1 (min_priority) and 10 (max_priority, the default value is 5 (norm_priority ).
V. Thread Synchronization
As multiple threads of the same process share the same storage space, access conflicts are also a serious problem. The Java language provides a dedicated mechanism to solve this conflict, effectively avoiding the simultaneous access of the same data object by multiple threads.
Because we can use private keywords to ensure that data objects can only be accessed by methods, we only need to propose a mechanism for methods. This mechanism is the synchronized keyword, which includes two usage methods: synchronized Method and synchronized block.
1. Synchronized Method: declare the Synchronized Method by adding the synchronized keyword to the method declaration. For example:
Public synchronized void accessval (INT newval );
The synchronized method controls access to class member variables: each class instance corresponds to a lock, and each synchronized method must obtain the lock of the class instance that calls the method before execution, otherwise the thread is blocked, once the method is executed, the lock is exclusive until the lock is released when the method is returned. Then, the blocked thread can obtain the lock and re-enter the executable status. This mechanism ensures that, at the same time, only one of all the member functions declared as synchronized is in the executable state (because at most only one member function can obtain the lock corresponding to this type of instance ), this effectively avoids access conflicts between class member variables (as long as all methods that may be used to invoke class member variables are declared as synchronized ).
In Java, not only are class instances, but each class also corresponds to a lock. In this way, we can declare the static member function of the class as synchronized, to control its access to static member variables of the class.
Synchronized: if a large method is declared as synchronized, the efficiency will be greatly affected. Typically, if you declare the thread-class method run () as synchronized, since the thread is always running throughout its life cycle, it will never succeed in calling any Synchronized Method of this class. Of course, we can put the code of the member variable of the category class into a special method, declare it as synchronized, and call it in the main method to solve this problem, but Java provides us with a better solution, that is, the synchronized block.
2. Synchronized block: Use the synchronized keyword to declare the synchronized block. Syntax:
Synchronized (syncobject ){
// Code that allows access control
}
The synchronized block is a code block in which the Code must obtain the lock of the object syncobject (as described earlier, it can be a class instance or class) before execution. The specific mechanism is described earlier. It is highly flexible because it can target any code block and can specify any locked object.
6. Thread Blocking
Java introduced a synchronization mechanism to solve access conflicts in shared storage areas. Now let's examine the access of multiple threads to shared resources. Obviously, the synchronization mechanism is not enough, because the resources required at any time are not necessarily ready for access, in turn, there may be more than one ready resource at the same time. To solve the access control problem in this case, Java introduces support for the blocking mechanism.
Blocking refers to suspending the execution of a thread to wait for a condition to occur (for example, a resource is ready). Students who have learned the operating system must be familiar with it. Java provides a large number of methods to support blocking. Let's analyze them one by one.
1. sleep () method: Sleep () allows you to specify a period of time in milliseconds as a parameter, which causes the thread to enter the blocking state within the specified time and cannot obtain the CPU time, after the specified time expires, the thread enters the executable state again.
Typically, sleep () is used to wait for a certain resource to be ready: after the test finds that the condition is not met, the thread is blocked for a period of time and then re-tested until the condition is met.
2. suspend () and resume () Methods: these two methods are used together. Suspend () causes the thread to enter the blocking state and will not be automatically restored. The corresponding resume () must be called, in order to re-enter the executable state of the thread. Typically, suspend () and resume () are used to wait for the results produced by another thread: after the test finds that the results have not yet been produced, the thread is blocked. After the results are produced by another thread, call resume () to restore it.
3. Yield () method: yield () causes the thread to discard the current CPU time, but does not block the thread. That is, the thread is still executable and the CPU time may be allocated again at any time. The effect of calling yield () is equivalent to that the scheduler considers that the thread has executed enough time to go to another thread.
4. wait () and notify () Methods: two methods are used together. Wait () causes the thread to enter the blocking state. It has two forms, one allows you to specify a period of time in milliseconds as the parameter, and the other does not have a parameter. The former is used as the corresponding notify () when it is called or exceeds the specified time, the thread re-enters the executable state, and the latter must be called with the corresponding notify.
It seems that they have no difference with the suspend () and resume () methods, but they are actually completely different. The core difference is that all the methods described above will not release the occupied lock (if occupied) when blocking, and this method is the opposite.
The above core differences lead to a series of detailed differences.
First, all the methods described above belong to the Thread class, but this pair is directly affiliated to the object class, that is, all objects have this pair of methods. It seems incredible at the beginning, but it is actually quite natural, because this pair of methods will release the occupied lock when blocking, and the lock is owned by any object, the wait () method of any object is called to cause thread blocking and the lock on the object is released. However, calling the notify () method of any object causes the wait () method of the object to be called () method ).
Secondly, all the methods described above can be called anywhere, but this pair of methods must be called in the synchronized method or block for a very simple reason, the lock can be released only when the current thread occupies the lock in the synchronized method or block. In the same way, the lock on the object that calls this method must be owned by the current thread so that the lock can be released. Therefore, this pair of method calls must be placed in such a synchronized method or block. The lock object of this method or block is the object that calls this pair of methods. If this condition is not met, the program can still be compiled, but the illegalmonitorstateexception will occur at runtime.
The preceding features of the wait () and notify () methods determine that they are often used together with the synchronized method or block, by comparing them with the inter-process communication machine of the operating system, we will find their similarity: the synchronized method or block provides functions similar to the operating system primitive, their execution will not be affected by the multi-thread mechanism, and this pair of methods is equivalent to the block and wakeup primitives (both of which are declared as synchronized ). The combination of these algorithms allows us to implement a series of exquisite inter-process communication algorithms (such as semaphore algorithms) on the operating system and solve various complicated inter-thread communication problems.
The wait () and notify () methods are described as follows:
First, the thread that calls the notify () method to remove blocking is randomly selected from the thread that is blocked by calling the wait () method of the object, we cannot predict which thread will be selected, so we should be very careful when programming to avoid problems caused by such uncertainty.
Second: In addition to notify (), there is also a method notifyall () that can also play a similar role. The only difference is that calling the notifyall () method will call the wait () and all the blocked threads are all blocked at one time. Of course, only the thread that gets the lock can enter the executable state.
When talking about blocking, we can't help but talk about deadlocks. A brief analysis shows that calls of the suspend () method and the wait () method without specifying the timeout period may lead to deadlocks. Unfortunately, Java does not support avoiding deadlocks at the language level. We must be careful in programming to avoid deadlocks.
We have analyzed various methods for implementing thread blocking in Java. We have focused on the wait () and notify () methods, because they have the most powerful functions, they are also the most flexible to use, but this also leads to low efficiency and error-prone. In actual use, we should use various methods flexibly to better achieve our goal.
7. daemon thread
A daemon is a special type of thread. It differs from a common thread because it is not the core part of an application. When all the non-daemon threads of an application terminate the operation, even if a daemon thread is still running, the application will be terminated. Otherwise, the application will not be terminated as long as a non-daemon thread is running. Daemon threads are generally used to provide services for other threads in the background.
You can call the isdaemon () method to determine whether a thread is a daemon thread, or call the setdaemon () method to set a thread as a daemon thread.
8. thread group
A thread group is a concept unique to Java. in Java, a thread group is a threadgroup-like object, and each thread belongs to a unique thread group, this thread group cannot be changed during the entire life cycle specified during the creation of the thread group. You can call the Thread class constructor that contains threadgroup parameters to specify the thread group. If this parameter is not specified, the thread is affiliated to the system thread group named system by default.
In Java, all thread groups except the pre-created system thread groups must be explicitly created. In Java, each thread group except the system thread group belongs to another thread group. You can specify the thread group to which it belongs when creating the thread group. If not, by default, it is affiliated to the system thread group. In this way, all threads are grouped into a tree with the system thread group as the root.
Java allows us to operate on all threads in a thread group at the same time. For example, we can set the priority of all threads by calling the corresponding methods of the thread group, it can also start or block all threads.
Another important role of Java's thread group mechanism is thread security. The thread group mechanism allows us to distinguish threads with different security features by grouping and process different threads in different groups, the layer structure of the thread group can also be used to support unequal security measures. The threadgroup class of Java provides a lot of methods to facilitate operations on every thread group in the thread group tree and every thread in the thread group.
IX. Summary
This article describes all aspects of Java multi-threaded programming, including creating threads and scheduling and managing multiple threads. We were deeply aware of the complexity of multi-threaded programming and the inefficiency of multi-threaded programs brought about by thread switching overhead. This also prompted us to seriously consider the question: do we need multithreading? When do I need multithreading?
The core of multithreading lies in the concurrent execution of multiple code blocks. The essential feature is that the Code between different code blocks is executed in disorder. Whether or not our program requires multithreading depends on whether it is also its internal characteristic.
If our program does not require concurrent execution of multiple code blocks, we naturally do not need to use multithreading. If our program requires concurrent execution of multiple code blocks, we do not need to execute them in disorder, we can use a loop to implement it in a simple and efficient way without using multithreading. Only when it fully complies with the characteristics of multithreading, the strong support of the multithreading mechanism for inter-thread communication and thread management can be used. It is worthwhile to use multithreading.