The defect of multi-thread programming on Java platform

Source: Internet
Author: User

From: http://www.softhouse.com.cn/news/show/104219.html

The defect of multi-thread programming on Java platform

Concurrent Programming in Java

For itself, concurrent programming is a technology that provides simultaneous execution of operations, whether on a single system or distributed on a large number of systems. These operations are actually commands, such as the subtasks of a single top-level task, which can be executed in parallel, as a thread, or as a process. The essential difference between a thread and a process is that the process is usually independent (such as an independent address space), so it can only interact through the inter-process communication mechanism provided by the system, threads usually share the status information of a single process and can directly share system resources and objects in memory.

You can use one of the following two methods to achieve concurrency through multiple processes. The first method is to run a process on the same processor and switch the context environment between processes processed by the operating system. (It can be understood that this type of switching is slower than the context switching between multiple threads in the same process .) The second method is to build large-scale parallel and complex distributed systems and run multiple processes on different physical processors.

From the perspective of built-in support, the Java language provides concurrent programming through threads; each JVM can support simultaneous execution by many threads. You can use either of the following methods to create a thread in Java:

Inherit from Java. Lang. Thread class. In this case, the run () method of the override subclass must contain the code for implementing the thread runtime. To execute this code, you need to instantiate the subclass object and then call the START () method of the object so that you can execute the run () method internally.

Create a custom implementation for the runnable interface. This interface contains only one run () method. In this method, you need to place the application code. To execute this code, you need to instantiate the object that implements the class, and then pass in the object as a constructor parameter when creating a new thread. Then, call the START () method of the newly created thread object to start executing the new thread.

Thread security and Synchronization

If a method in a Java object can run securely in a multi-threaded environment, this method is called thread-safe. To achieve this security, there must be a mechanism through which multiple threads running the same method can synchronize their operations. In this way, when accessing the same object or code line, only one thread is allowed to be processed. This synchronization requires the thread to use objects called signals to communicate with each other.

There is a type of signal called mutex signal or mutex. As the name suggests, the ownership of this signal object is mutually exclusive. That is to say, at any specified time, only one thread can have a mutex. Other threads that want to obtain ownership will be blocked. They must wait until the threads that have the mutex release the mutex. If multiple threads queue in sequence to wait for the same mutex, when the current owner releases it, only one waiting thread can get it; other threads will continue to block.

In the early 1970 s, C. a.r. Hoare and others developed a concept called Monitor. A monitor is a code entity, and its access is protected by mutex. Any thread that wants to execute this code must obtain the associated mutex at the top of the code block and release it at the bottom. Because only one thread can have mutex at a specified time, this effectively ensures that only the thread with it can execute the monitor code block. (Protected Code does not need to be adjacent. For example, every object in Java has a monitor associated with it .)

Any developer who wants to program threads in the Java language will immediately regard the above content as the effect of the synchronized keyword. Make sure that the Java code contained in the synchronized block is executed by only one thread at a specified time. Internally, the synchronized keyword can be converted from the runtime to a certain situation: All competing threads attempt to obtain them (referring to threads) the (only) mutex associated with the object instance being operated. The thread that gets the mutex will run the code, and then release the mutex when exiting the synchronized block.

Waiting and notification

Wait/notify construction also plays an important role in the inter-thread communication mechanism of Java. The basic idea is that a condition required by a thread can be promoted by another thread. In this way, the wait condition can be met. Once the condition is true, the thread that triggers the condition will wait for the thread to wake up and continue from where it is paused.

The wait/notify mechanism is more difficult to understand and judge than the synchronized mechanism. To determine the behavior logic of the wait/notify method, you must determine the logic of all the methods using it. One method is used to determine one method at a time and isolate it from other methods. This is a reliable way to draw incorrect conclusions about the overall system behavior. Obviously, the complexity of doing so will increase rapidly as the number of methods to be judged increases.

Thread status

As mentioned above, you must call the START () method of the newly created thread to start its execution. However, simply calling the START () method does not mean that the thread starts running immediately. This method only changes the thread state from new to runnable. The thread state changes to running (from runnable) only when the operating system really schedules thread execution ).

A typical operating system supports two thread models: Collaborative and preemptible. In a collaborative model, each thread has a final opinion on how long it will take to retain its control over the CPU and when it will give up. In this model, because a rogue thread may occupy control, other threads may never run. In a preemptible model, the operating system uses a timer based on the clock tick. Based on this timer, the operating system can forcibly transfer control from one thread to another. In this case, the scheduling policy that determines which thread will obtain the next control may be based on various indicators, such as the relative priority, the length of time a thread has been waiting for execution, and so on.

If for some reason, a thread in the running state needs to wait for a resource (for example, waiting for the input data of the device to arrive, or waiting for a notification that certain conditions have been set ), or it is blocked when trying to obtain the mutex, so the thread decides to sleep, then it can enter the blocked state. When the sleep period expires, the expected input arrives, or the current owner of the mutex releases it and notifies the waiting thread that it can capture the mutex again, the blocked thread enters the runnable state again.

When the run () method of the thread is completed (or a normal return, or an unknown exception such as runtimeexception is thrown), the thread terminates. In this case, the thread state is dead. When a thread dies, it cannot be restarted by calling its start () method again. If so, an invalidthreadstateexception exception will be thrown.

Four Common Defects

As I have already demonstrated, multi-threaded programming in Java is implemented through a large number of well-designed structures supported by the language. In addition, a large number of design patterns and Guiding Principles are designed to help people understand the many defects brought about by such complexity. In addition, multi-threaded programming can easily inadvertently introduce minor bugs into multi-threaded code. More importantly, it is very difficult to analyze and debug such problems. Next, we will introduce a list of the most common problems that will be encountered (or may have been encountered) during multi-threaded programming in Java.

Competition Conditions

Competition conditions are said to exist in such a system: competition for shared resources exists between multiple threads, and the winner determines the behavior of the system. Allen Holub in his article "programming Java threads in the real world" provides a simple multi-threaded program example with such a bug. Another even more terrible consequence of incorrect synchronization between conflicting access requests is data crash. At this time, part of the shared data structure is updated by one thread, the other part is updated by another thread. In this case, the system does not act according to the intent of the winning thread, and the system does not act according to the intent of any thread. Therefore, both threads end up failing.

Deadlock

A deadlock occurs when a thread waits for a certain condition to become real (for example, a resource can be used), but the waiting condition cannot become real, the reason is that the condition becomes true. The thread is waiting for the first thread to "do something ". In this way, both threads are waiting for the other side to take the first step, so they cannot do anything.

Activity lock

Unlike a deadlock, an active lock occurs when the thread actually works, but the work has not been completed yet. This usually occurs when two threads work at the same time, so the work done by the first thread is canceled by the other thread. A simple example is that each thread already has an object, and another object owned by another thread is required. It can be imagined that each thread puts down its own objects and picks up the objects put down by another thread. Obviously, these two threads will always run on the lock operation, and the result is nothing. (A common example is that two people encounter each other in a narrow corridor. Everyone politely asks the other side to let the other side first, but at the same time they get to the same side, so neither of them can pass. This situation will last for some time, and then the two people will flash from here to there, but the result is still not making any progress .)

Resource Depletion

Resource depletion, also known as thread depletion, is the consequence that the wait/notify primitive in Java cannot guarantee live-ness. Java forces these methods to have the lock on the objects they wait for or notify. The wait () method called on a thread must release the monitor lock before it starts to wait. After returning the lock from the method and receiving the notification, it must be re-acquired. Therefore, the Java language specification describes a wait set related to each object in addition to the lock itself ). Once the thread releases the lock on the object (after wait is called), the thread will be placed in this waiting set.

Most JVM implementations put the waiting thread in the queue. Therefore, if other threads are waiting for the monitor when a notification occurs, a new thread will be placed at the end of the queue, and it is not the next thread to obtain the lock. Therefore, when the notified thread actually obtains the monitor, the conditions for notifying the thread may no longer be true, so it has to wait again. This situation may be infinitely sustained, resulting in a waste of computing work (because the thread needs to be put into the waiting set and retrieved from it repeatedly) and thread depletion.

The fable of greedy philosophers

The prototype used to demonstrate this behavior is Professor Peter Welch's description of "smart people do not have chicken ". In this scenario, the system is considered as a school composed of five philosophers, a cook, and a canteen. All philosophers (except one) need to think about it (in the code example, it takes 3 seconds to consider it) before they go to the canteen to get food. The greedy philosopher does not want to waste his time on thinking-on the contrary, he returns to the canteen again and again in an attempt to get chicken to eat.

The cook prepares chicken in four batches. Each batch is sent to the canteen. Greedy philosophers go to the kitchen constantly, but they always miss the food! This is the case: when he first arrived, it was too early and the cook was not yet on fire. Therefore, the greedy philosopher had to wait (called through the wait () method ). When the meal was opened (called using the Y () method), the greedy philosopher waited in the canteen again. But this time, when he came to wait, his four colleagues had arrived, so he was behind them in the canteen queue. His colleagues took all four portions of chicken from the kitchen, so the greedy philosopher had to wait. Poor (or fair), he is always out of this loop.

Verification problems

Generally, it is difficult to verify Java-programmed multi-threaded programs according to common specifications. Similarly, developing automation tools for common concurrency problems (such as deadlocks, activity locks, and resource depletion) complete and simple analysis is not easy-especially in any Java program or in the absence of a formal model of concurrency.

What's worse, concurrency problems are constantly changing and difficult to track. Every Java developer has heard of such a Java program (or has written it in person): After strict analysis, it has been running normally for quite some time and has not shown a potential deadlock. Then suddenly one day, the problem occurred, and the development team experienced a lot of sleepless nights trying to find and fix the root cause.

On the one hand, the errors that may easily occur in multi-threaded Java programs are not obvious and may occur at any time. On the other hand, it is entirely possible that these bugs never appear in the program. The problem depends on unknown factors. The complex nature of multi-threaded programs makes it difficult for people to effectively verify them. There is no ready-made rule to identify such problems in multi-threaded code, and it cannot be proved exactly that these problems do not exist. As a result, many Java developers have completely avoided the design and development of multi-threaded applications, that is to say, it is great to model the system using the concurrent and parallel methods, and they do not use multithreading.

Developers who really want to program with multiple threads usually have prepared the following or two solutions (at least part ):

It took a long time to test the code, find out all the concurrency problems, and sincerely hope that all these problems have been discovered and fixed when the application is actually running.

A large number of operating design patterns and guiding principles for multi-threaded programming. However, such guiding principles are only effective when the entire system is designed according to their specifications, and no design rules can cover all types of systems.

Although few people know about it, there is a third option for writing (and then verifying) Correct multi-threaded applications. Using the mathematical theory of precise thread synchronization called Communicating Sequential Processes (CSP), it is best to deal with problems such as deadlocks and active locks during design. CSP was designed by C. a.r. Hoare and later in 1970s. CSP provides an effective method to prove that a system built with its construction and tools can avoid common problems of concurrency.

Conclusion

In this comprehensive introduction to CSPs for Java programmers, I will focus on the first step to overcome common problems in multi-threaded application development, that is, understanding these issues. I have introduced the multi-threaded programming structures currently supported on the Java platform, explained their origins, and discussed some possible problems with such programs. I also explained using formal theories to clear these problems in any, large, and complex applications (I .e., competitive adventures, deadlocks, activity locks, and resource depletion) or the difficulty of proving that these problems do not exist.

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.