How to write multi-threaded Java applications avoid the most common problems in the current programming

zhaozj2021-02-08  459

Almost all draw programs written in AWT or SWING need multi-threaded. However, multithreading procedures can cause many difficulties. Developers who have just started programming often find them tortured by some problems, such as incorrect program behavior or dead locks.

In this article, we will explore the problems that use multithreading and propose solutions for common traps.

What is the thread? A program or process can contain multiple threads that can perform corresponding instructions based on the program's code. Multi-threaded seems to be in parallel to perform their respective work, just like running multiple processes on a computer. When you implement multithreading on a multi-process machine computer, they do in parallel. Different from the process, thread sharing address space. That is, multiple threads can read and write the same variable or data structure.

When writing multithreaded programs, you must pay attention to whether each thread has interfered with other threads. Programs can be regarded as an office. If you do not need to share office resources or communicate with others, all staff will work independently. If a staff wants to talk to others, and when the staff is "listening" and both say the same language. In addition, when the copying machine is idle and in the available state (not only half of the copying work, there is no problem such as paper blocking), the staff can use it. In this article, you will see that threads collaborate in the Java program seem to be a staff member working in a good organization.

In multi-threaded programs, threads can be obtained from the ready-to-read queue and run on the available system CPU. The operating system can move the thread from the processor to the ready to queue or block queue, which can be considered that the processor "hangs" the thread. Similarly, the Java Virtual Machine (JVM) can also control the movement of the thread - in collaboration or preemptive model - move the process from the ready-to-write queue to the processor, so the thread can start executing its program code.

Collaborative thread model allows threads to determine when to give up the processor to wait for other threads. The program developer can accurately determine when a thread will hang by other threads, allowing them to work with the other party. A disadvantage is that some malicious or write threads consume all available CPU times, causing other threads "hungry".

In the preemptive thread model, the operating system can interrupt the thread at any time. It is usually interrupted after it has been running (it is a so-called time piece). Such a result is naturally no threads that can be unfairly long-term. However, it is possible to interrupt the thread at any time will bring other troubles to the program developer. Also use the office, assuming that a staff will use a copy in front of another person, but the print job left when it is not completed, and the other person can then have the previous staff to stay on the copy. data. The preemptive thread model requires the thread to share resources correctly, and the collaborative model requires the thread sharing execution time. Since the JVM specification does not specify a thread model, the Java developer must write programs that can run correctly on both models. After understanding some aspects of threads and inter-thread communication, we can see how to design programs for these two models.

Threads and Java languages ​​To create threads using Java language, you can generate a THREAD class (or its subclass) object and send a START () message to this object. (The program can send a START () message to any class object derived from the Runnable interface.) 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 the traditional program; threads continue to run until Run () returns, at this time, the thread is dead.

Most applications require threads to communicate with each other to synchronize their actions. The easiest way to implement synchronization in the Java program is to lock. To prevent simultaneous access to shared resources, threads can be locked and unlocked to the resource before and after using resources. If you want to lock the copy machine, only one staff member has a key at any time. A copier cannot be used if there is no key. The locking of the shared variable allows the Java thread to communicate and synchronize quickly and easily. If a thread is a lock to an object, you can know that there is no other thread to access the object. Even in the preemptive model, other threads are not able to access this object until the threads of the locked thread are awakened, complete the work and unlock. Those threads trying to access a locked object usually enter the sleep state until the thread unlocking is locked. Once the lock is opened, these sleep processes will be awakened and moved to the ready-to-read queue. In Java programming, all objects are locked. Threads can be used to get the lock using the synchronized keyword. The code blocks of the given classes for a given class can only be performed by one thread at any time. This is because the code requires the lock of the object before executing. Continue to our metaphor for the copy machine, in order to avoid copying conflicts, we can simply synchronize the copy resource. As the following code example, only one staff member is allowed to use copy resources at any time. Modify the copy machine by using the method (in the Copier object). This method is synchronous method. Only one thread can perform synchronous code in a Copier object, so those who need to use the Copier object must wait in line.

Class CopyMachine {

Public Synchronized Void Makecopies (Document D, INT NCOPIES) {

// only one thread Executes this at a time

}

Public void loadpaper () {

// Multiple Threads Could Access this at Once!

Synchronized (this) {

// only one thread Accesses this at a time

// Feel Free To Use Shared Resources, Overwrite Members, ETC.

}

}

}

The Fine-Grain lock is usually a relatively rough method. Why do you want to lock the entire object without allowing other threads to use other synchronization methods in the object to access shared resources? If an object has multiple resources, it is not necessary to lock all threads outside only to let a thread use some of the resources. Since each object is locked, you can use the virtual object to be locked 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

}

}

If you synchronize in the method level, the entire method cannot be declared as the synchronized keyword. They use a member lock, not an object-level lock that the SYNCHRONIZED method is available.

Signal volume is usually, there may be a number of resources that need multiple threads need to access the number of resources. I want to run a number of threads that answer client requests on the server. These threads need to connect to the same database, but only a certain number of database connections can only be obtained at any time. How can you effectively assign these fixed number of database connections to a large number of threads? A method of controlling access to a set of resources (except for simply unlocking), is the use of a well-known semaphore. The semapcular count will encapsulate a group of resources available. The signal is implemented on the basis of a simple upper lock, which is equivalent to a counter that can make threads, and initialize the number of available resources. For example, we can initialize a semaphore to the available database connections. Once a thread obtains a signal, the number of database connections can be obtained. The thread consumes the resource and releases the resource, the counter will add one. When all resources control of the semaphore have been occupied, if there is a thread attempt to access this semaphore, it will enter the blocking state until there is available resource released. The most common usage of semaphors is to solve the "consumer-producer problem". When a thread is working, this problem may occur if another thread is accessible to the same shared variable. Consumer threads can only be able to access data after production of producer threads. With the amount of semaphore to solve this problem, you need to create a signal initialized to zero, so that the consumer thread occurs when this signal is accessed. Whenever the unit is completed, the producer thread sends signals (release resources). Whenever the consumer thread consumes the unit production results and requires a new data unit, it tries to get the semaphore again. Therefore, the value of the semaphore is always equal to the number of data units that can be consumed by the production. This approach is much more efficient than the use of consumer threads to keep check whether there is a data unit. Because the consumer thread wakes up, if no available data unit is found, it will re-enter the sleep state, such an operating system overhead is very expensive.

Although the signal is not supported directly by Java language, it is easy to implement it on the basis of the lock. A simple implementation 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 ;

Notify (); // Alert a Thread That's Blocking On this Semaphore

}

}

Common locked problems unfortunately, using the lock will bring other problems. Let us see some common problems and corresponding solutions:

Dead lock. The deadlock is a classic multi-threaded problem, because different threads are waiting for those locks that are not unlikely to be released, resulting in all works that cannot be completed. Suppose there are two threads, representing two hungry people, they must share the knife and fork and take turns. They all need to get two locks: shared knives and locks of shared forks. If the thread "a" gets a knife, the thread "b" has obtained fork. Thread A will enter the blocking state to wait for the fork, and the thread B is blocked to wait for the knife from the A. This is just an example of artificial design, but although it is difficult to detect during runtime, this type often occurs. Although it is very difficult to detect or scrutinize the various situations, as long as the system is designed according to the following rules, it is possible to avoid deadlocks:

Let all threads get a set of locks in the same order. This method eliminates the problem of the resources of X and Y wait for the other party's resources. Multiple locks form a group and put it in the same lock. In the example of deadlocks, you can create a lock of a silver object. The lock must be obtained before obtaining a knife or fork. The available resources that will not be blocked can be marked. When a thread gets a lock of the silverware object, you can judge whether the object lock in the entire silver collection can be obtained by checking the variable. If so, it can get the associated lock, otherwise, it is necessary to release this lock of the silverware and try again later. Most importantly, carefully designing the entire system carefully before writing code. Multithreading is difficult, and the detailed design system can help you to discover deadlocks before starting programming. Volatile variable.

The volatile keyword is a Java language to optimize the compiler. The following code is:

Class volatiletest {

Public void foo () {

Boolean flag = false;

IF (flag) {

// this could happen

}

}

}

An optimized compiler may determine whether the statement of the IF section will never be executed, and this part is not compiled. If this class is accessed by multi-threaded, the FLAG is set after a thread in front, and can be reset by other threads before it is tested by the IF statement. Use the volatile keyword to declare the variable, you can tell the compiler When compiling, you do not need to optimize this part of the code by predicting the variable value.

Unable to access threads sometimes do not have problems with object locks, threads still have possible access to blocking states. IO is the best example of this type of problem in Java programming. This object should still be accessed by other threads when blocking IO calls within the object. This object is usually responsible for canceling this blocking IO operation. Threads that cause the blocking call often fail to fail the synchronization task. If other methods of the object are also synchronized, this object is equivalent to being frozen when the thread is blocked. Other threads cannot send messages (for example, cancel IO operations) because they cannot get locks of the object. It is important to ensure that those blocking calls are not included in the sync code, or confirmed that there is a non-synchronization method in an object with synchronous blocking code. Although this method takes some attention to ensure the results code safely, it allows the object to be able to respond to other threads after blocking the object's thread.

Design judgment for different thread models is a grab or collaborative thread model, depending on the implementation of the virtual machine, and is different depending on various implementations. Therefore, Java developers must write programs that can work on both models.

As mentioned earlier, the thread can be interrupted in any part of the code in the preemptive model, unless it is an atomic operation code block. Once the code segment in the atomic operating code block is executed, it is necessary to perform before the thread is exchanged. In Java programming, a variable space assigned a less than 32-bit is an atomic operation, and it is not an atomic assignment of two 64-bit data types like Double and long. Using the lock to properly synchronize the access to shared resources, it is enough to ensure that a multi-thread program works correctly under the preemptive model.

In the collaborative model, whether the thread is guaranteed to abandon the processor, and the execution time of other threads is not plundered, it is entirely on the programmer. Call Yield () method The current thread can be removed from the processor into the ready-to-read queue. Another method is to call the SLEEP () method, allowing the thread to abandon the processor, and sleep within the time interval specified in the SLEEP method.

As you think, these methods will be placed in place in the code and cannot guarantee normal work. If the thread is having a lock (because it is in a synchronization method or code block), this lock cannot be released when it calls yield (). This means that even if this thread has been hang, wait for this lock release from other threads that still can't continue to run. In order to alleviate this problem, it is best not to call the Yield method in the synchronous method. Packing those that need to be synchronized in a synchronization block, there is no non-synchronous method, and Yield is called outside of these synchronization code blocks. Another solution is to call the wait () method, allow the processor to abandon the lock it currently owns. This method can work well if the object is synchronized at the method level. Because it only uses a lock. Wait () will not be abandoned if it uses a Fine-Grained lock. In addition, a thread that blocks the wait () method, only when other threads call notifyAll ().

Threads and AWT / SWING In the Java program that creates a Swing and / or AWT package, the AWT event handle runs in its own thread. The developer must pay attention to avoid tie these GUI threads with a time-consuming calculation work, because these threads must be responsible for handling user time and redrawing the user graphical interface. In other words, once the GUI thread is busy, the entire program looks like there is no response state. Swing threads notify those Swing Callback (such as Mouse Listener and Action Listener) by calling the appropriate method. This method means that Listener should use the Listener Callback method to generate other threads to complete this job in any case. The purpose is to let Listener Callback return more quickly, allowing Swing threads to respond to other events.

If a Swing thread is not able to run synchronously, respond to events and redraw output, how can other threads securely modify Swing status? As mentioned above, Swing Callback runs in a Swing thread. So they can modify Swing data and painted to the screen.

But what should I do if it is not the change caused by Swing Callback? Use a non-Swing thread to modify Swing data is unsafe. Swing provides two ways to solve this problem: InvokeLater () and InvokeAther (). In order to modify the Swing state, just simply call one of the methods, let Runnable objects do these work. Because Runnable objects are usually their own threads, you may think that these objects are executed as threads. But it is actually unsafe. In fact, Swing places these objects in the queue and performs its RUN method at a moment. This will be able to safely modify the Swing status.

Summarize the design of the Java language so that multi-threads are necessary for almost all applets. In particular, IO and GUI programming require multi-thread to provide users with a perfect experience. If you are in accordance with some of the basic rules mentioned in this article, you can carefully design the system before starting programming - including its access to shared resources, you can avoid many common and difficult thread traps.

data

转载请注明原文地址:https://www.9cbs.com/read-830.html

New Post(0)