Multithreaded Java program common error
Author: Yuliang Song Source: programming enthusiasts
In almost all programming languages, due to multi-threaded errors have difficulty reproduction, the deadlock of the program or other multi-threaded errors may only appear in some special situations, or run the same on different VMs. The error is different when the program is different. Therefore, when writing multithreaded programs, it is particularly important in advance. Whether it is a client or a server-side multithreaded Java program, the most common multi-threaded problem includes deadlock, implicit deadlock, and data competition. The deadlock lock is such a situation: multiple threads are blocked simultaneously, one or all of them are waiting for a resource released. Since the thread is blocked indefinitely, the program is not possible to terminate normally. The root caused by deadlocks is to use the "synchronized" keyword to manage access to specific objects with the "Synchronized" keyword. The "synchronized" keyword is to ensure that only one thread is allowed to perform a specific code block at some point, thus the thread that is allowed must first have access to the valence or object. When thread access objects, threads are locked to objects, and this lock causes other threads that also want to access the same object to be blocked until the first thread releases it on the object. For this reason, when using the "synchronized" keyword, it is easy to appear two threads waiting to make each other to make a certain action. Code One is a simple example of a deadlock. // code for a class Deadlocker {int field_1; private Object lock_1 = new int [1]; int field_2; private Object lock_2 = new int [1]; public void method1 (int value) { "synchronized" (lock_1) { "synchronized "(LOCK_2) {Field_1 = 0; Field_2 = 0;}}}}} public void method2 (INT VALUE) {" Synchronized "(Lock_2) {" Synchronized "(LOCK_1) {Field_1 = 0;}}}}}}}}}}}}}}}} Reference Code First, consider the following process: ◆ Threada calls Method1 (). ◆ Threada synchronizes on Lock_1, but allows it to be preemptive. ◆ Another threadB will start execution. ◆ Threadb calls Method2 (). ◆ Threadb gets LOCK_2, continues to execute, attempt to get Lock_1. But ThreadB cannot obtain Lock_1 because Threada occupies Lock_1. ◆ Now, threadb blocked because it is waiting for Threada to release Lock_1. ◆ Now it is now in Threada to continue. Threada tried to get Lock_2, but it was not successful because Lock_2 has been occupied by ThreadB. ◆ Threada and threadb are blocked, and the program is deadlock. Of course, most deadlocks will not be so easy, you need to carefully analyze the code to see, especially for larger multithreaded programs. A good thread analysis tool, such as Jprobe Threadalyzer to analyze deadlocks and point out the code location that produces a problem. Recessive dead lock hidden dead locks due to irregular programming methods, but not necessarily each test is deadlocked. For this reason, some hidden deadlocks may be discovered after the application is officially released, so its harm is greater than ordinary deadlocks. The following describes two cases that lead to hidden deadlocks: the order of chain, and possess and wait.
The order in which multiple concurrent threads are at the same time at the same time, respectively, and there will be cases of locking. If a thread accounts for another thread, there is a deadlock. Considering the following scenarios, Threada and ThreadB two threads need to have Lock_1, Lock_2 two locks, and the locking process may be as follows: ◆ Threada gets Lock_1; ◆ Threada is preempted, the VM scheduler is transferred to threadb; ◆ threadb Gets Lock_2; ◆ Threadb is preemptive, VM scheduler is transferred to Threada; ◆ Threada tries to get Lock_2, but Lock_2 is posted by threadb, so Threada blocks; ◆ The scheduler is turned to threadb; ◆ ThreadB tries to get Lock_1, but Lock_1 is occupied by threada, so threadb blocks ; ◆ Threada and Threadb deadlock. It must be pointed out that in the case where the code does not change, some time the deadlock process does not appear, the VM scheduler may make one of the threads simultaneously obtain two locks, that is, the thread gets two locks. Not interrupted. In this case, routine deadlock detection is difficult to determine the error. Posterate and wait if a thread will wait for a notification from another thread, there may be another hidden deadlock, consider code 2. // Code two public class queue {static java.lang.Object queueLock_; Producer producer_; Consumer consumer_; public class Producer {void produce () {while { "synchronized" (queueLock_) {produceItemAndAddItToQueue () (done!); "Synchronized "(Consumer_) {consumer_.notify ();}}}} public class consumer {consume () {while (!" {"SYNCHRONIZED" (consumer_) {consumer_.wait ();} RemoveItemFromQueueAndProcessit ();}}}}}} In Code II, Producer adds new content to the queue so that it handles new content. The problem is that consumer may keep the lock on the queue, block the Producer access queue, even when the CONSUER waits for the notification of the Producter, will continue to remain locked. This way, because Producer cannot add new content to the queue, and consumer is waiting for the product to join the new content, resulting in a deadlock. The lock posted in the waiting time is a hidden deadlock, because things may develop -Producer threads in accordance with the ideal case. No need to be occupied by Consumer. Despite this unless there is absolutely reliable reason, this programming method is still unsafe. Sometimes "possession and wait" may also trigger a series of threads waiting, for example, thread a occupying the lock required by thread B and waits, and the thread b has a lock required by thread c and waits.
To correct the code two error, simply modify the Consumer class, remove Wait () "Synchronized" (). Data competition data competition is caused by the lack of or inappropriate use of synchronization mechanisms when accessing a shared resource (such as variable). If there is no correctly defining a certain thread to access the variable, data competition will occur, and the competitive thread will get an access license, but will result in unpredictable results. Since the running operation can be interrupted at any time (ie the run will be preempted by other threads), so it cannot be assumed that the thread that starts to run first is always the thread that is running first, first accessing the data shared by both. In addition, on different VMs, the scheduling mode of the thread may also be different, making data competition problems more complicated. Sometimes data competition does not affect the final results of the program, but at other times, it is possible to lead to unpredictable results. Beneficial data competition is not all data competition. Consider the example of the code three. Suppose gethouse () returns to all the threads, you can see that competition will appear here: Bricklayer is read from House.FoundationReady_, and FoundationPourara is written to House.FoundationReady_. // Code three public class House {public volatile boolean foundationReady_ = false;} public class FoundationPourer extends Thread {public void run () {House a = getHouse (); a.foundationReady_ = true;}} public class BrickLayer extends Thread {public Void Run () {house a = gethouse (); while (! a.foundationReady_) {Try {thread.sleep (500);} catch (exception e) {system.err.println ("Exception:" E); }}}}} Despite competition, based on Java VM specification, Boolean data read and write is principled, that is, the VM cannot interrupt the read or write operations of the thread. Once the data changes are successful, there is no need to change it back to the original data (no need to "back"), so the data competition of code three is a benign competition, and the code is safe. The malignant data competition first looks at the code four.
// Code four public class Account {private int balance_; // account balance public int getBalance (void) {return balance _;} public void setBalance (int setting) {balance_ = setting;}} public class CustomerInfo {private int numAccounts_; private Account [] accounts_; public void withdraw (int accountNumber, int amount) {int temp = accounts_ [accountNumber] .getBalance (); temp = temp - amount; accounts_ [accountNumber] .setBalance (temp);} public void deposit (int Accountnumber, int AMOUNT) {int Temp = Accounts_ [AccountNumber] .getBalance (); temp = temp amount; accounts_ [accountnumber] .setbalance (temp);}} If husband A and wife b try to pass different bank teller machines What happens to the same account? Let us assume that the initial balance of the account is 100 yuan, and a possible implementation of the program is taken. B deposit 25 yuan, her teller machine began to execute deposit (). First, the current balance 100 is obtained, and the balance is stored locally at the local temporary variable, and then the temporary variable is plus 25, and the value of the temporary variable becomes 125. Now, the thread scheduler interrupts the thread before calling setBalance (). A deposits 50 yuan. When B is still in a suspended state, the side begins to execute deposit (): getBalance () Returns 100 (because the thread of B has not written in the modified balance), A thread is in existing balance Based on the 500 to get 150, and save 150 to the temporary variable. The thread is then interrupted before calling setBalance (). Now, the thread of B is running, writing the value (125) saved in the temporary variable to the balance, the teller machine tells B that the transaction is completed, the account balance is 125 yuan. Next, the thread of A continues to operate, and the value of the temporary variable is written to the balance, the teller machine tells A to say that the transaction is completed, the account balance is 150 yuan. What is the final result? B The deposit disappears, just like B has not saved money. Perhaps someone will think that you can change getBalance () and setBalance () to the synchronization method to protect Account.balance_ to resolve data competition. In fact, this method is not going. "Synchronized" keywords ensure that only one thread is only one thread to execute the getBalance () or setBalance () method at the same time, but this cannot block another thread to modify the account balance during a thread operation. To properly use the "Synchronized" keyword, you must recognize that the entire transaction process is not interfered with another thread, not just a step of data access. Therefore, the key to this example is that after a thread gets the current balance, to ensure that other threads cannot modify the balance until the balance processing of the first thread is completed. The correct modification method is to change deposit () and withdraw () into a synchronization method. Dead lock, hidden deadlock and data competition are the most common errors in Java multi-threaded programming.