Multithreading in Java programs is much easier to use than C or C ++, because the Java programming language provides language-level support. This article uses simple programming examples to illustrate how intuitive multithreading is in Java programs. After reading this article, you should be able to write simple multi-threaded programs.
Why wait in queue?
The following simple Java program completes four unrelated tasks. Such a program has a single control thread that controls linear movement between the four tasks. In addition, because of the required resources? Printers, disks, databases, and displays-each task contains a significant wait time due to hardware and software limitations. Therefore, the program must wait for the printer to complete the task of printing files before accessing the database. If you are waiting for the completion of the program, this is a poor use of computing resources and your time. One way to improve this program is to make it multi-threaded.
Four unrelated tasks
Class myclass { Static public void main (string ARGs []) { Print_a_file (); Manipulate_another_file (); Access_database (); Draw_picture_on_screen (); } } |
In this example, each task must wait for the previous task to complete before it starts, even if the involved task is irrelevant. However, in real life, we often use multi-threaded models. While dealing with some tasks, we can also let our children, spouse, and parents complete other tasks. For example, when I write a letter, I may send my son to the post office to buy stamps. In terms of software, this is called multiple control (or execution) threads.
You can use two different methods to obtain multiple control threads:
Multiple processes
Multiple processes can be created in most operating systems. When a program starts, it can create a process for each task to be started and allow them to run simultaneously. When a program is blocked because it is waiting for network access or user input, another program can run, which increases resource utilization. However, creating a process in this way requires a certain cost: setting a process takes a considerable amount of CPU time and memory resources. In addition, most operating systems do not allow processes to access the memory space of other processes. Therefore, inter-process communication is inconvenient and does not provide it to easy programming models.
Thread
A thread is also called a Lightweight Process (lwp ). Because threads can only be active within the scope of a single process, it is much cheaper to create a thread than to create a process. In this way, because threads allow collaboration and data exchange, and are very cheap in computing resources, threads are more desirable than processes. Threads must be supported by the operating system, so not all machines provide threads. Java programming language, as a new language, has integrated thread support with the language itself, which provides strong support for threads.
Use Java programming language to implement threads
Java programming languages make multithreading so simple and effective that some programmers say it is actually natural. Although it is much easier to use threads in Java than in other languages, there are still some concepts that need to be mastered. One important thing to remember is that the main () function is also a thread and can be used for useful work. A programmer must create a new thread only when multiple threads are required.
Thread class
The thread class is a specific class, that is, it is not an abstract class, which encapsulates the thread behavior. To create a thread, the programmer must create a new class to export from the Thread class. The programmer must overwrite the thread's run () function to do useful work. Instead of calling this function directly, you must call the START () function of thread and then call run (). The following code illustrates its usage:
Create two new threads
Import java. util .*; Class timeprinter extends thread { Int pausetime; String name; Public timeprinter (int x, string N ){ Pausetime = X; Name = N; } Public void run (){ While (true ){ Try { System. Out. println (name + ":" + new Date (system. currenttimemillis ())); Thread. Sleep (pausetime ); } Catch (exception e ){ System. Out. println (E ); } } } Static public void main (string ARGs []) { Timeprinter TP1 = new timeprinter( 1000, "Fast guy "); Tp1.start (); Timeprinter TP2 = new timeprinter( 3000, "slow guy "); Tp2.start (); } } |
In this example, we can see a simple program that displays the current time on the screen at two different time intervals (1 second and 3 seconds. This is done by creating two new threads, including three main () threads. However, because sometimes the class to run as a thread may already be part of a class hierarchy, you cannot create a thread using this mechanism. Although you can implement any number of interfaces in the same class, Java programming language only allows one class to have a parent class. At the same time, some programmers avoid exporting from the Thread class because it imposes a class hierarchy. In this case, the runnable interface is required.
Runnable interface
This interface has only one function, run (), which must be implemented by the class that implements this interface. However, when running this class, its semantics is slightly different from the previous example. We can use the runnable interface to rewrite the previous example. (Different parts are expressed in black .)
Create two new threads without imposing class Layers
Import java. util .*; Class timeprinter implements runnable { Int pausetime; String name; Public timeprinter (int x, string N ){ Pausetime = X; Name = N; } Public void run (){ While (true ){ Try { System. Out. println (name + ":" + new Date (system. currenttimemillis ())); Thread. Sleep (pausetime ); } Catch (exception e ){ System. Out. println (E ); } } } Static public void main (string ARGs []) { Thread T1 = new thread (New timeprinter (1000, "Fast guy ")); T1.start (); Thread t2 = new thread (New timeprinter (3000, "slow guy ")); T2.start (); } } |
Note that when you use the runnable interface, you cannot directly create the object of the required class and run it. You must run it from an instance of the thread class. Many programmers prefer the runnable interface, because inheritance from the Thread class imposes class levels.
Synchronized keyword
So far, the examples we have seen are simply using threads in a very simple way. There is only the smallest data stream, and two threads will not access the same object. However, in most useful programs, there is usually information flow between threads. Consider a financial application, which has an account object, as shown in the following example:
Multiple activities in a bank
Public class account { String holdername; Float amount; Public Account (string name, float AMT ){ Holdername = Name; Amount = AMT; } Public void deposit (float AMT ){ Amount + = AMT; } Public void withdraw (float AMT ){ Amount-= AMT; } Public float checkbalance (){ Return amount; } } |
In this sample code, an error is lurking. If this type is used in a single-threaded application, there will be no problem. However, in multi-threaded applications, different threads may simultaneously access the same account object. For example, the owner of a federated account can simultaneously access the same account on different ATMs. In this case, the deposit and expenditure may occur in this way: one transaction is overwritten by another transaction. This situation will be disastrous. However, the Java programming language provides a simple mechanism to prevent such coverage. Each object has an associated lock at runtime. This lock can be obtained by adding the key word synchronized to the method. In this way, the modified account object (as shown below) will not suffer errors such as data corruption:
Synchronize multiple activities in a bank
Public class account { String holdername; Float amount; Public Account (string name, float AMT ){ Holdername = Name; Amount = AMT; } Public synchronized void deposit (float AMT ){ Amount + = AMT; } Public synchronized void withdraw (float AMT ){ Amount-= AMT; } Public float checkbalance (){ Return amount; } } |
Both deposit () and withdraw () functions require this lock to operate, so when one function runs, another function is blocked. Note that checkbalance () is strictly a READ function. Because checkbalance () is not synchronized, no other methods will block it, and no other methods will be blocked, regardless of whether the methods have been synchronized.
Support for advanced multithreading in Java programming language
Thread group
Threads are created individually, but they can be classified into thread groups for debugging and monitoring. A thread can be associated with a thread group only when it is created. In a program that uses a large number of threads, it may be helpful to use a thread group to organize threads. They can be viewed as directories and file structures on computers.
Send messages between threads
When a thread needs to wait for a condition before continuing execution, only the synchronized keyword is not enough. Although the synchronized keyword prevents concurrent update of an object, it does not implement inter-thread mail. The object class provides three functions: Wait (), Policy (), and policyall (). Take the global climate prediction program as an example. These programs divide the earth into many units. In each cycle, the calculation of each unit is isolated until these values tend to be stable, and then some data is exchanged between adjacent units. Therefore, in essence, each thread in each loop must wait for all threads to complete their respective tasks before entering the next loop. This model is called blocking synchronization. The following example illustrates this model:
Shield Synchronization
Public class bsync { Int totalthreads; Int currentthreads; Public bsync (int x ){ Totalthreads = X; Currentthreads = 0; } Public synchronized void waitforall (){ Currentthreads ++; If (currentthreads <totalthreads ){ Try { Wait (); } Catch (exception e ){} } Else { Currentthreads = 0; Policyall (); } } } |
When wait () is called for a thread, the thread is blocked only until the other thread calls y () or yyall () for the same object. Therefore, in the previous example, different threads call the waitforall () function after completing their work, and the last thread triggers the policyall () function, this function will release all threads. The third function notify y () only one waiting thread. This function is useful when you restrict access to resources that can only be used by one thread at a time. However, it is impossible to predict which thread will receive this notification, because it depends on the Java Virtual Machine (JVM) scheduling algorithm.
Give CPU to another thread
When a thread abandons a rare resource (such as a database connection or network port), it may call the yield () function to temporarily lower its priority so that other threads can run.
Daemon thread
There are two types of threads: User thread and daemon thread. User threads are the threads that complete useful work. Daemon threads are threads that only provide auxiliary functions. The thread class provides the setdaemon () function. The Java program will run to all user threads to terminate, and then it will destroy all the daemon threads. In the Java Virtual Machine (JVM), the program can continue to run even if another user thread is still running after the main end.
Avoid unrecommended Methods
We do not recommend that you use methods that are retained to support backward compatibility. They may or may not appear in future versions. Java multithreading support has been significantly revised in versions 1.1 and 1.2. The stop (), suspend (), and resume () functions are no longer recommended. These functions may introduce subtle errors in JVM. Although function names may sound attractive, resist the temptation not to use them.
Debug a threaded Program
In a threaded program, some common and annoying situations may occur: deadlocks, live locks, memory corruption, and resource depletion.
Deadlock
Deadlock may be the most common problem in multi-threaded programs. A deadlock occurs when one thread needs a resource and the other thread holds the lock of the resource. This situation is usually difficult to detect. However, the solution is quite good: Get all resource locks in the same order in all threads. For example, if there are four resources? A, B, C, and D? In addition, a thread may need to obtain the Lock of any of the four resources. Make sure that the lock of A is obtained first before obtaining the Lock of B, and so on. If "thread 1" wants to obtain the locks for B and C, and "thread 2" gets the locks for A, C, and D, this technology may cause blocking, but it will never cause deadlocks on these four locks.
Live lock
A life lock occurs when a thread is busy accepting new tasks and never has the chance to complete any tasks. This thread will eventually exceed the buffer and cause program crash. Imagine a secretary needs to input a letter, but she has been busy answering the phone, so this letter will never be entered.
Memory Corruption
If you use the synchronized keyword wisely, you can avoid memory errors.
Resource Depletion
Some system resources are limited, such as file descriptors. Multi-threaded programs may use up resources because every thread may want such a resource. If the number of threads is large, or the number of waiting threads for a resource exceeds the number of available resources, it is best to use the resource pool. The best example is the database connection pool. As long as the thread needs to use a database connection, it will retrieve one from the pool and then return it back to the pool. A resource pool is also called a resource pool.
Debug a large number of threads
Sometimes it is very difficult to debug a program because a large number of threads are running. In this case, the following class may come in handy:
Public class probe extends thread { Public probe (){} Public void run (){ While (true ){ Thread [] x = new thread [100]; Thread. enumerate (X ); For (INT I = 0; I <100; I ++ ){ Thread t = x [I]; If (t = NULL) Break; Else System. Out. println (T. getname () + "/t" + T. getpriority () + "/T" + T. isalive () + "/t" + T. isdaemon ()); } } } } |
Restrict thread priority and Scheduling
The Java thread model involves the thread priority that can be dynamically changed. Essentially, the priority of a thread is a number ranging from 1 to 10. The larger the number, the more urgent the task is. The JVM standard calls a thread with a higher priority before calling a thread with a lower priority. However, the processing of threads with the same priority is random. How to handle these threads depends on the operating system policy at the grassroots level. In some cases, threads with the same priority run at a time. In other cases, the threads run until the end. Keep in mind that JAVA supports 10 priorities, and the base-level operating system may have a much lower priority, which may cause some confusion. Therefore, priority can only be used as a rough tool. The final control can be done by wise using the yield () function. Generally, do not rely on the thread priority to control the thread status.
Summary
This article describes how to use a thread in a Java program. A more important issue like whether to use a thread depends on the application at hand. One way to determine whether to use multithreading in an application is to estimate the amount of code that can be run in parallel. Remember the following points:
Multithreading does not increase the CPU capability. However, if the local thread of JVM is used for implementation, different threads can run simultaneously on different processors (in multiple CPU machines), so that multiple CPU machines can be fully utilized.
If applications are computing-intensive and restricted by CPU functions, only multiple CPU machines can benefit from more threads.
Multithreading is usually advantageous when the application must wait for slow resources (such as network connections or database connections), or when the application is non-interactive.
Internet-based software must be multi-threaded; otherwise, the user will feel the application is slow to reflect. For example, multithreading can make programming easier when developing a server that supports a large number of clients. In this case, each thread can serve different customers or customer groups, thus reducing the response time.
Some programmers may have used threads in C and other languages, and there is no language support for threads in those languages. These programmers may usually lose confidence in the thread.