在編寫多線程應用程序時,遇到的最多見問題之一是競爭條件。 java
我對社區的問題是: 編程
什麼是比賽條件? 您如何檢測到它們? 您如何處理它們? 最後,如何防止它們發生? 數據結構
當設備或系統試圖同時執行兩個或多個操做時,競爭狀態是一種不但願出現的狀況,可是因爲設備或系統的性質,必須按照正確的順序進行操做才能被執行。正確完成。 多線程
在計算機內存或存儲中,若是幾乎在同一時刻接收到讀取和寫入大量數據的命令,而且機器嘗試覆蓋部分或所有舊數據,而仍舊保留舊數據,則可能會發生競爭狀態讀。 結果多是如下一種或多種:計算機崩潰,「非法操做」,程序的通知和關閉,讀取舊數據時出錯或寫入新數據時出錯。 併發
微軟實際上已經發布了有關種族條件和僵局問題的很是詳細的文章 。 其中最歸納的摘要是標題段落: oracle
當兩個線程同時訪問一個共享變量時,就會發生競爭狀態。 第一個線程讀取變量,第二個線程從變量讀取相同的值。 而後,第一個線程和第二個線程對值執行操做,而後爭先看哪一個線程能夠最後將值寫入共享變量。 保留最後寫入其值的線程的值,由於該線程正在覆蓋前一個線程寫入的值。 函數
請嘗試如下基本示例,以更好地瞭解比賽條件: 工具
public class ThreadRaceCondition { /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { Account myAccount = new Account(22222222); // Expected deposit: 250 for (int i = 0; i < 50; i++) { Transaction t = new Transaction(myAccount, Transaction.TransactionType.DEPOSIT, 5.00); t.start(); } // Expected withdrawal: 50 for (int i = 0; i < 50; i++) { Transaction t = new Transaction(myAccount, Transaction.TransactionType.WITHDRAW, 1.00); t.start(); } // Temporary sleep to ensure all threads are completed. Don't use in // realworld :-) Thread.sleep(1000); // Expected account balance is 200 System.out.println("Final Account Balance: " + myAccount.getAccountBalance()); } } class Transaction extends Thread { public static enum TransactionType { DEPOSIT(1), WITHDRAW(2); private int value; private TransactionType(int value) { this.value = value; } public int getValue() { return value; } }; private TransactionType transactionType; private Account account; private double amount; /* * If transactionType == 1, deposit else if transactionType == 2 withdraw */ public Transaction(Account account, TransactionType transactionType, double amount) { this.transactionType = transactionType; this.account = account; this.amount = amount; } public void run() { switch (this.transactionType) { case DEPOSIT: deposit(); printBalance(); break; case WITHDRAW: withdraw(); printBalance(); break; default: System.out.println("NOT A VALID TRANSACTION"); } ; } public void deposit() { this.account.deposit(this.amount); } public void withdraw() { this.account.withdraw(amount); } public void printBalance() { System.out.println(Thread.currentThread().getName() + " : TransactionType: " + this.transactionType + ", Amount: " + this.amount); System.out.println("Account Balance: " + this.account.getAccountBalance()); } } class Account { private int accountNumber; private double accountBalance; public int getAccountNumber() { return accountNumber; } public double getAccountBalance() { return accountBalance; } public Account(int accountNumber) { this.accountNumber = accountNumber; } // If this method is not synchronized, you will see race condition on // Remove syncronized keyword to see race condition public synchronized boolean deposit(double amount) { if (amount < 0) { return false; } else { accountBalance = accountBalance + amount; return true; } } // If this method is not synchronized, you will see race condition on // Remove syncronized keyword to see race condition public synchronized boolean withdraw(double amount) { if (amount > accountBalance) { return false; } else { accountBalance = accountBalance - amount; return true; } } }
競爭條件和數據競爭之間存在重要的技術差別。 大多數答案彷佛都假設這些術語是等效的,但事實並不是如此。 單元測試
當2條指令訪問相同的存儲器位置時發生數據爭用,這些訪問中的至少一個是寫操做,而且在這些訪問之間進行排序以前沒有發生任何狀況 。 如今,關於在順序以前發生的事件的爭論不少,可是一般在同一鎖定變量上的ulock-lock對和在同一條件變量上的wait-signal對會致使發生先於順序。 測試
競爭條件是語義錯誤。 這是在事件的時間安排或順序中出現的缺陷,致使錯誤的程序行爲 。
許多競爭條件多是(其實是)數據競爭引發的,但這不是必需的。 實際上,數據爭用和爭用條件既不是彼此的必要條件也不是充分條件。 這篇博客文章還經過一個簡單的銀行交易示例很好地解釋了差別。 這是另外一個簡單的示例 ,解釋了它們之間的區別。
如今咱們已經肯定了術語,讓咱們嘗試回答原始問題。
因爲種族條件是語義錯誤,所以沒有檢測它們的通用方法。 這是由於在一般狀況下,沒法使用自動的oracle來區分正確的程序行爲與錯誤的程序行爲。 種族檢測是一個沒法肯定的問題。
另外一方面,數據競爭具備不必定與正確性相關的精肯定義,所以人們能夠檢測到它們。 數據爭用檢測器有不少類型(靜態/動態數據爭用檢測,基於鎖集的數據爭用檢測,基於事前發生的數據爭用檢測,混合數據爭用檢測)。 最早進的動態數據競爭檢測器是ThreadSanitizer ,在實踐中效果很好。
一般,處理數據爭用須要必定的編程紀律來誘發(在開發期間或使用上述工具檢測到它們之間)訪問共享數據之間的邊緣以前。 這能夠經過鎖,條件變量,信號量等來完成。可是,也能夠採用不一樣的編程範例,例如消息傳遞(而不是共享內存)來避免構造過程當中的數據爭用。
什麼是比賽條件?
您打算在下午5點去看電影。 您在下午4點詢問門票的供應狀況。 該表明說,他們有空。 放映前5分鐘,您能夠放鬆身心併到達售票窗口。 我敢確定,您能夠猜想會發生什麼:這是一間完整的房子。 這裏的問題在於檢查和操做之間的持續時間。 您在4諮詢並在5採起行動。與此同時,其餘人則搶了票。 那是比賽條件-特別是比賽條件的「先檢查後行動」場景。
您如何檢測到它們?
宗教代碼審查,多線程單元測試。 沒有捷徑。 不多有Eclipse插件出現,可是尚未穩定的東西。
您如何處理和預防它們?
最好的辦法是建立無反作用的無狀態函數,並儘量多地使用不可變對象。 但這並不老是可能的。 所以,使用java.util.concurrent.atomic,併發數據結構,正確的同步以及基於actor的併發性將有所幫助。