Write multi-threaded Java applications-how to avoid the most common problems in current programming

Source: Internet
Author: User

Almost all drawing programs written using AWT or swing require multithreading. However, multi-threaded programs may cause many difficulties. developers who are new to programming often find that they are suffering from problems such as incorrect program behavior or deadlocks.

In this article, we will discuss the problems encountered when using multithreading and propose solutions to common traps.

What is a thread?
A program or process can contain multiple threads, which can execute corresponding commands Based on the program code. Multithreading seems to be executing their respective jobs in parallel, just like running multiple processors on a computer. When multithreading is implemented on a multi-processor computer, they can indeed work in parallel. Unlike the process, the thread shares the address space. That is to say, multiple threads can read and write the same variable or data structure.

When writing multi-threaded programs, you must pay attention to whether each thread interferes with the work of other threads. The program can be seen as an office. If you do not need to share office resources or communicate with others, all employees will work independently and concurrently. If an employee wants to talk to others, and only when the employee is "listening" and both of them speak the same language. In addition, employees can use the photocopier only when it is idle and available (not only half of the copy work is completed, no problems such as paper blocking. In this article, you will see that the threads that collaborate in Java programs are like employees who work in a well-organized organization.

In multi-threaded programs, threads can be obtained from the ready queue and run on the available system CPU. The operating system can move the thread from the processor to the ready queue or block the queue. In this case, the processor "suspends" the thread. Similarly, Java Virtual Machine (JVM) can also control the movement of threads ?? In collaboration or preemptive model ?? The process is moved from the ready queue to the processor, so the thread can start to execute its program code.

The collaborative thread model allows the thread to decide when to discard the processor to wait for other threads. Developers can accurately determine when a thread will be suspended by other threads and allow them to cooperate effectively with each other. The disadvantage is that some malicious or poorly written threads consume all the available CPU time, resulting in other threads being "Hungry ".

In the preemptible thread model, the operating system can interrupt the thread at any time. It is usually interrupted after it runs for a period of time (called a time slice. The result is that no thread can occupy the processor for a long time. However, interrupting the thread at any time may cause other troubles for the program developer. In the same example of using the office, assume that a clerk snatched another person to use the photocopier, but the print work left when the work was not completed, and the other person continued to use the photocopier, there may be materials from the previous employee on the photocopier. The preemptible thread model requires threads to correctly share resources, while the collaborative model requires threads to share the execution time. Since the JVM specification does not specify a thread model, Java developers must write programs that can run correctly on both models. After learning about threads and communication between threads, we can see how to design programs for these two models.

Thread and Java language
To create a thread in Java, you can generate an object of the thread class (or its subclass) and send a start () message to the object. (The program can send a start () message to any class object derived from the runnable interface .) The definition of each thread action is included in the run () method of the thread object. The run method is equivalent to the main () method in a traditional program. The thread will continue to run until run () returns, and the thread will die.

Lock
Most applications require threads to communicate with each other to synchronize their actions. The simplest way to implement synchronization in Java is locking. To prevent simultaneous access to shared resources, the thread can lock and unlock the resource before and after the resource is used. Imagine locking the photocopier. At any time, only one employee has a key. The photocopier cannot be used without a key. Locking shared variables enables Java threads to communicate and synchronize quickly and conveniently. If a thread locks an object, it can know that no other thread can access the object. Even in a preemptible model, other threads cannot access this object until the locked thread is awakened, finished, and unlocked. The threads trying to access a Lock Object usually go to sleep until the lock thread locks. Once the lock is opened, these sleep processes will be awakened and moved to the ready queue.

In Java programming, all objects are locked. The thread can use the synchronized keyword to obtain the lock. A method or synchronous code block can only be executed by one thread for a given class instance at any time. This is because the Code requires an object lock before execution. To avoid duplication conflicts, we can simply synchronize the copy resources. As in the following code example, only one employee is allowed to use the copy resource at any time. You can use the method (in the copier object) to modify the photocopier status. This method is the synchronization method. Only one thread can execute the synchronization code in a copier object. Therefore, employees who need to use the copier object must wait in queue.

Class copymachine {
Public synchronized void makecopies (document D, int ncopies ){
// Only one thread executes this at a time
}
Public void loadpaper (){
// Multiple threads cocould access this at once!
Synchronized (this ){
// Only one thread accesses this at a time
// Feel free to use shared resources, overwrite members, etc.
}
}
}

Fine-grain lock
Locking at the object level is usually a rough method. Why is it necessary to lock the entire object and not allow other threads to temporarily use other Synchronization Methods in the object to access Shared resources? If an object has multiple resources, you do not need to lock all threads out to allow a thread to use some of the resources. Because each object has a lock, you can use a virtual object to lock it as follows:

Class finegrainlock {
Mymemberclass X, Y;
Object xlock = new object (), ylock = new object ();
Public void Foo (){
Synchronized (xlock ){
// Access X here
}
// Do something here-but don't use shared resources
Synchronized (ylock ){
// Access y here
}
}
Public void bar (){
Synchronized (this ){
// Access both X and Y here
}
// Do something here-but don't use shared resources
}
}

For method-level synchronization, the entire method cannot be declared as the synchronized keyword. They use Member locks instead of Object-level locks that can be obtained by the synchronized method.

Semaphores
Generally, multiple threads may need to access a small number of resources. Assume that several threads are running on the server to answer client requests. These threads need to connect to the same database, but only a certain number of database connections can be obtained at any time. How can you effectively allocate these fixed numbers of database connections to a large number of threads? One way to control access to a group of resources (except for simple on-ground locks) is to use the well-known counting semaphore ). Semaphore counts encapsulate the management of a group of available resources. Semaphores are implemented on the basis of simple locking, which is equivalent to a counter that can ensure thread security and initialize as the number of available resources. For example, we can initialize a semaphore to obtain the number of database connections. Once a thread obtains a semaphore, the number of available database connections is reduced by one. When the thread consumes the resource and releases the resource, the counter will add one. When all resources controlled by semaphores are occupied, if a thread attempts to access this semaphores, the system will be blocked until available resources are released.

The most common use of semaphores is to solve the "consumer-producer problem ". This problem may occur if another thread accesses the same shared variable when it is working. The consumer thread can only access data after the producer thread completes production. To solve this problem by using semaphores, you need to create a semaphores whose Initialization is zero so that the consumer thread can access this semaphores with blocking. The producer thread sends a signal (releasing resources) to the semaphore each time it completes unit work ). Whenever a consumer thread consumes the unit production result and requires a new data unit, it will attempt to obtain the semaphore again. Therefore, the semaphore value is always equal to the number of data units that can be consumed after production. This method is much more efficient than the consumer thread does not stop checking whether there are available data units. After a consumer thread wakes up, if no available data unit is found, it will go to sleep again. Such an operating system overhead is very expensive.

Although semaphores are not directly supported by the Java language, they are easily implemented on the basis of locking objects. A simple implementation method is as follows:

Class semaphore {
Private int count;
Public semaphore (int n ){
This. Count = N;
}
Public synchronized void acquire (){
While (COUNT = 0 ){
Try {
Wait ();
} Catch (interruptedexception e ){
// Keep trying
}
}
Count --;
}
Public synchronized void release (){
Count ++;
Y (); // alert a thread that's blocking on this semaphore
}
}

Common locking Problems
Unfortunately, locking may cause other problems. Let's look at some common problems and corresponding solutions:

Deadlock. Deadlock is a classic multi-thread problem, because different threads are waiting for locks that cannot be released at all, so that all work cannot be completed. Suppose there are two threads, representing two hungry people respectively. They must share the knives and forks and take turns to eat. They all need two locks: the shared knife and the shared cross lock. If the thread "A" gets the knife, and the thread "B" gets the cross. Thread a will enter the blocking status to wait for the cross to be obtained, and thread B will be blocked to wait for the knife owned by thread. This is just an example of human design, but it often happens even though it is difficult to detect it at runtime. Although it is very difficult to detect or repeat various situations, the deadlock problem can be avoided as long as the system is designed according to the following rules:

Let all threads obtain a set of locks in the same order. This method eliminates the issue that the owner of X and Y waits for the resources of the other party.

Group multiple locks and place them under the same lock. In the previous deadlock example, you can create a silver object lock. Therefore, you must obtain the silver lock before getting a knife or a cross.

Mark the available resources that are not blocked with variables. When a thread obtains the Lock of a silver object, it can check the variables to determine whether the object lock in the entire silver set can be obtained. If yes, it can obtain the relevant lock. Otherwise, it will release the silver lock and try again later.

Most importantly, carefully design the entire system before writing the code. Multithreading is difficult. Designing a system in detail before you start programming can help you avoid the difficulty of discovering deadlocks.

The volatile variable. Volatile keyword is designed by the Java language to optimize the compiler. The following code is used as an example:

Class volatiletest {
Public void Foo (){
Boolean flag = false;
If (FLAG ){
// This cocould happen
}
}
}

An optimized compiler may determine that the if statement will never be executed and the code will not be compiled. If this class is accessed by multiple threads, the flag can be reset by other threads after it is set by a previous thread and before it is tested by the if statement. When variables are declared using the volatile keyword, the compiler can be told that the code is optimized by predicting the variable value during compilation.

A thread that cannot be accessed may be blocked even if it obtains the object lock. Io is the best example of this type of problem in Java programming. When the thread is blocked due to Io calls in the object, the object should still be accessed by other threads. This object is usually responsible for canceling this blocking Io operation. The thread that causes blocking calls often causes synchronization tasks to fail. If other methods of the object are synchronized, the object will be frozen when the thread is blocked. Other threads cannot send messages to the object because they cannot obtain the object lock (for example, cancel the IO operation ). Make sure that the synchronization code does not contain those blocking calls, or that a non-synchronous method exists in an object that uses the synchronization blocking code. Although this method requires some attention to ensure the safe operation of the result code, it allows the object to still respond to other threads after the thread with the object is blocked.

Design for different thread Models
Determining whether the thread model is preemptible or collaborative depends on the real-time users of the Virtual Machine and varies with various implementations. Therefore, Java developers must write programs that can work on two models.

As mentioned above, in a preemptible model, a thread can be interrupted in the middle of any part of the code unless it is an atomic code block. The code segment in the atomic operation code block is executed before the thread is replaced with a processor. In Java programming, allocating a variable space smaller than 32 bits is an atomic operation, and the allocation of the 64-bit data types such as double and long is not atomic. Using locks to correctly synchronize access to shared resources ensures that a multi-threaded program works correctly in a preemptible model.

In a collaborative model, whether the thread can discard the processor normally and not plunder the execution time of other threads depends entirely on the programmer. The yield () method can be called to remove the current thread from the processor to the ready queue. The other method is to call the sleep () method so that the thread can discard the processor and sleep at the specified interval in the sleep method.

As you might think, leaving these methods somewhere in the Code is not guaranteed to work properly. If a thread is holding a lock because it is in a synchronous method or code block, it cannot be released when it calls yield. This means that even if the thread has been suspended, other threads waiting for the lock to be released cannot continue to run. To alleviate this problem, it is best not to call the yield method in the synchronization method. Package the code to be synchronized in a synchronization block, which does not contain non-synchronous methods and calls yield only outside of these Synchronous Code blocks.

Another solution is to call the wait () method so that the processor can discard the Lock of its current object. This method works well if the object is synchronized at the method level. Because it only uses one lock. If it uses fine-grained locks, wait () will not be able to discard these locks. In addition, a thread that is blocked by calling the wait () method is awakened only when other threads call policyall.

Thread and AWT/swing
In Java programs that use swing and/or AWT packages to create a GUI, the AWT event handle runs in its own thread. Developers must be careful not to bind these GUI threads with time-consuming computing tasks, because these threads must be responsible for processing user time and redrawing the user GUI. In other words, once the GUI thread is busy, the whole program looks like a unresponsive state. Swing threads call appropriate methods to notify those swing callback (such as mouse listener and action listener ). This method means that the listener should use the listener callback method to generate other threads no matter how many things are to be done. The objective is to allow listener callback to return more quickly and allow swing threads to respond to other events.

If a swing thread cannot synchronously run, respond to events and redraw the output, how can other threads safely modify the swing state? As mentioned above, swing callback runs in swing threads. Therefore, they can modify swing data and draw it to the screen.

But what if it is not a change in swing callback? It is not safe to use a non-swing thread to modify swing data. Swing provides two methods to solve this problem: invokelater () and invokeandwait (). To modify the swing state, you just need to call one of the methods to let the runnable object do the work. Because runnable objects are usually their own threads, you may think that these objects will be executed as threads. But it is not safe to do that. In fact, swing will put these objects in the queue and execute its run method at some point in the future. In this way, the swing status can be safely modified.

Summary
The Design of Java makes multithreading necessary for almost all applets. In particular, both Io and GUI programming require multithreading to provide users with a perfect experience. If you follow several basic rules mentioned in this article and carefully design the system before starting programming ?? Including its access to shared resources, you can avoid many common and hard-to-find thread traps.

Materials

Refer to the API specification (1.3 standard) on the Java 2 platform: Java 2 API documentation.
For more information about JVM thread and lock processing, see Java virtual machine specification.
Allen Holub's taming Java threads (apress, June 2000) is an excellent reference book.
You may also want to read Allen's article if I am king: Suggestions on solving Java programming language thread problems (developerworks, October 2000 ), it addresses some issues that he calls "the weakest part of a great language.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.