Why are you waiting in line?
The following simple Java program accomplishes four unrelated tasks. Such a program has a single control thread that controls the linear movement between these four tasks. In addition, because the required resources-printers, disks, databases, and displays-have intrinsic latency due to hardware and software limitations, each task contains significant wait times. Therefore, the program must wait for the printer to complete the task of printing files before accessing the database, and so on. If you are waiting for a program to complete, this is a poor use of computing resources and your time. One way to improve this program is to make it multithreaded.
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 task involved is irrelevant. However, in real life, we often use multithreaded models. While we are dealing with certain tasks, we can also allow children, spouses and parents to accomplish other tasks. For example, I may send my son to the post office to buy stamps at the same time as I write. In software terminology, this is referred to as multiple control (or execution) threads.
There are two different ways to get 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 that is about to start and allow them to run concurrently. When a program is blocked by waiting for network access or user input, another program can run, which increases resource utilization. However, there is a cost to creating each process in this way: setting up a process consumes a significant portion of processor time and memory resources. Also, most operating systems do not allow processes to access the memory space of other processes. Therefore, interprocess communication is inconvenient and does not provide itself to an easy programming model.
- Thread
A thread is also known as a lightweight process (LWP). Because threads can only be active within the scope of a single process, creating a line turndown creation process is much cheaper. This way, threads are preferable to processes because they allow collaboration and data exchange, and are very inexpensive in terms of computing resources. Threads require the support of the operating system, so not all machines provide threads. The Java programming language, as a fairly new language, has integrated threading support with the language itself, providing robust support for threads.
Implementing threads using the Java programming language
The Java programming language makes multithreading so simple and effective that some programmers say it is actually natural. Although using line turndown in Java is much easier in other languages, there are still some concepts that need to be mastered. One important thing to keep in mind is that the main () function is also a thread and can be used to do useful work. Programmers need to create new threads only when they need multiple threads.
Thread class
The thread class is a concrete class, which is not an abstract class that encapsulates the behavior of a thread. To create a thread, the programmer must create a new class that is exported from the thread class. The programmer must override the Thread's run () function to do useful work. Instead of calling this function directly, the user must call the start () function of the Thread, which then calls run (). The following code illustrates its use:
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 (for "Fast Guy"); Tp1.start (); Timeprinter TP2 = new Timeprinter ("Slow guy"); Tp2.start (); }}
In this example, we can see a simple program that displays the current time on the screen at two different intervals (1 seconds and 3 seconds). This is done by creating two new threads, including main () a total of three threads. However, because sometimes classes that you want to run as threads may already be part of a class hierarchy, you can no longer create threads by this mechanism. Although you can implement any number of interfaces in the same class, the Java programming language allows only one class to have one parent class. At the same time, some programmers avoid exporting from the Thread class because it imposes class hierarchies. In this case, the interface must be runnable.
Runnable interface
This interface has only one function, run (), and this function must be implemented by the class that implements the interface. However, in the case of running this class, its semantics are slightly different from the previous one. We can overwrite the previous example with the Runnable interface. (different parts are represented in bold.) )
Create two new threads without imposing a class hierarchy
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 (. "Fast guy"); T1.start (); thread t2 = new Thread (New Timeprinter, "Slow guy"); T2.start (); }}
Note that when using the Runnable interface, you cannot directly create an object of the desired class and run it; You must run it from within an instance of the Thread class. Many programmers prefer the runnable interface because inheriting from the Thread class imposes a class hierarchy.
Synchronized keywords
So far, the examples we've seen are simply taking advantage of threads in a very simple way. There is only minimal data flow, and there is no case where two threads access the same object. However, in most useful programs, there is usually a flow of information between threads. Try to consider a financial application that has an account object, as shown in the following example:
A number of 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; }}
There is an error lurking in this code sample. If this class is used for single-threaded applications, there will be no problem. However, in the case of multi-threaded applications, it is possible for different threads to access the same account object at the same time, for example, the owner of a federated accounts accesses concurrently on different ATMs. In this case, deposit and expense may occur in this way: one transaction is overwritten by another. This situation will be disastrous. However, the Java programming language provides a simple mechanism to prevent such overrides from occurring. Each object has an associated lock at run time. This lock can be obtained by adding the keyword synchronized to the method. This way, the revised account object (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; }}
The deposit () and withdraw () functions require this lock to operate, so when one function is running, another function is blocked. Note that checkbalance () is strictly a read function without making changes. Because Checkbalance () is not synchronized, no other method blocks it, nor does it block any other methods, regardless of whether the methods are synchronized.
Advanced multithreading support in the Java programming language
Thread Group
Threads are created individually, but they can be categorized into thread groups for debugging and monitoring purposes. It can be associated with a thread group only while the thread is being created. In programs that use a large number of threads, it can be helpful to use thread groups to organize threads. They can be thought of as directories and file structures on your computer.
Sending between threads
When a thread waits for a condition before it continues execution, only the Synchronized keyword is sufficient. Although the Synchronized keyword prevents concurrent updates of an object, it does not implement inter-threading . The Object class provides three functions for this purpose: Wait (), notify (), and Notifyall (). Take the global Climate Prediction program as an example. These programs divide the Earth into many units, and in each cycle, each cell is evaluated in isolation until the values stabilize, and then some data is exchanged between adjacent cells. Therefore, in essence, each thread in each loop must wait for all threads to complete their respective tasks before they can enter the next loop. This model is called masking synchronization , and the following example illustrates this model:
Masking Sync
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; Notifyall ();}}}
When wait () is called on a thread, the thread is effectively blocked until another thread calls notify () or Notifyall () to the same object. Therefore, in the previous example, different threads will call the Waitforall () function after completing their work, and the last thread will trigger the Notifyall () function, which will release all threads. The third function, notify (), notifies only one waiting thread, which is useful when access restrictions are made on resources that can only be used by one thread at a time. However, it is not possible to predict which thread will get this notification, because it depends on the Java virtual machine (JVM) scheduling algorithm.
To cede the CPU to another thread
When a thread discards a scarce resource, such as a database connection or a network port, it may call the yield () function to temporarily lower its priority so that some other thread can run.
Daemon Threads
There are two types of threads: the user thread and the daemon thread. The user thread is the thread that completes the useful work. Daemon Threads are those that provide only accessibility. The Thread class provides the Setdaemon () function. The Java program will run to all user threads to terminate, and then it will break all the daemon threads. In a Java virtual machine (JVM), even after main ends, the program can continue to run if another user thread is still running.
Avoid the use of non-advocating methods
Methods that are not advocated are those that are retained to support backward compatibility, which may or may not appear in future releases. Java multithreading support has made significant revisions in version 1.1 and version 1.2, and the Stop (), suspend (), and resume () functions have not been advocated for use. These functions may introduce subtle errors in the JVM. Although function names may sound tempting, resist temptation to use them.
Debugging a threaded Program
Some of the common and annoying situations that can occur in inline programs are deadlocks, live locks, memory corruption, and resource exhaustion.
Dead lock
Deadlocks can be the most common problem with multithreaded programs. A deadlock occurs when a thread needs a resource and another thread holds the lock on that resource. This situation is often difficult to detect. However, the solution is pretty good: get all the resource locks in the same order in all the threads. For example, if you have four resources ―a, B, C, and d― and one thread may want to acquire a lock on any one of the four resources, make sure to get the lock on a first before acquiring the lock on B, and so on. If thread 1 wants to acquire locks on B and C, and thread 2 acquires a, C, and D locks, this technique can cause blocking, but it never causes a deadlock on these four locks.
Live lock
A live lock occurs when a thread is busy accepting a new task so that it never has a chance to complete any task. This thread will eventually exceed the buffer and cause the program to crash. Imagine a secretary needs to enter 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 the problem of an air-dead person with memory errors altogether.
Resource Exhaustion
Some system resources are limited, such as file descriptors. Multithreaded threads may run out of resources because each thread may want to have one such resource. If the number of threads is quite large, or if the number of candidate threads for a resource far exceeds the number of available resources, it is best to use a resource pool . One of the best examples is the database connection pool. Whenever a thread needs to use a database connection, it takes one out of the pool and returns it to the pool later. Resource pools are also known as resource Libraries .
Debugging a large number of threads
Sometimes a program is very difficult to debug because it has a large number of threads running. In this case, the following class might come in handy:
public class Probe extends Thread {public Probe () {} public void Run () {while (true) { thread[] x = new T HREAD[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 ()); } } }}
Restricting thread priority and scheduling
The Java threading model involves thread priorities that can be dynamically changed. In essence, a thread's priority is a number from 1 to 10, and the larger the number, the more urgent the task is. The JVM standard first calls a higher-priority thread before calling a lower-priority thread. However, the standard processing of threads with the same priority is random. How these threads are handled depends on the base-level operating system policy. In some cases, threads with the same priority are run at the same time; in other cases, the thread will run until the end. Keep in mind that Java supports 10 priorities, and the base-level operating system support may be much less prioritized, which can cause some confusion. Therefore, you can only use the priority as a very coarse tool. The final control can be accomplished by judicious use of the yield () function. In general, do not rely on thread precedence to control the state of threads.
Transferred from: http://www.ibm.com/developerworks/cn/java/multithreading/
Multithreading in Java programs (GO)