您是否注意到 Java 多線程並發示例中的競爭條件? 如何處理?
已發表: 2019-12-11 前段時間我寫了一篇關於生產者消費者示例以及如何在 Java 中更好地處理讀/寫操作的文章。 同樣,在本教程中,我們將討論有關Race Condition
和Thread locking
的內容。
如果您有以下任何問題,那麼您來對地方了:
- Java 競爭條件示例
- 互斥量 java 例子
- 多線程——什麼是競爭條件?
- 比賽條件和關鍵部分
- 什麼是比賽條件?
- 如何通過示例處理 Java 中的競爭條件
為什麼會出現 Java 中的競爭條件?
Java 中的競態條件發生在two or more threads
嘗試修改/更新共享數據same time
。
讓我們看一下下面的程序邏輯:
這是一個非常簡單的銀行示例,您將在其中
deposit
和withdraw
100 times
。 您將總共存入 $100 100 次 = $100 x 100 = $10,000 並且您將總共提取 $50 100 次 = $50 x 100 = $5,000。 計劃完成時,您的銀行中應該有 5000 美元。
以下是步驟:
- 創建類 CrunchifyRaceCondition.java
- 創建類 CrunchifyTransaction.java
- 創建類 CrunchifyBankAccount.java
- 我們將運行 CrunchifyRaceCondition 類,它將啟動存款和取款循環 100 次。
- 我們將
with Synchronized block
運行它以檢查結果 - 我們將在
without Synchronized block
的情況下運行它來檢查結果
CrunchifyRaceCondition.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
package com . crunchify . tutorial ; /** * @author Crunchify.com * */ public class CrunchifyRaceCondition { public static void main ( String [ ] args ) { CrunchifyBankAccount crunchifyAccount = new CrunchifyBankAccount ( "CrunchifyAccountNumber" ) ; // Total Expected Deposit: 10000 (100 x 100) for ( int i = 0 ; i < 100 ; i ++ ) { CrunchifyTransaction t = new CrunchifyTransaction ( crunchifyAccount , CrunchifyTransaction . TransactionType . DEPOSIT_MONEY , 100 ) ; t . start ( ) ; } // Total Expected Withdrawal: 5000 (100 x 50) for ( int i = 0 ; i < 100 ; i ++ ) { CrunchifyTransaction t = new CrunchifyTransaction ( crunchifyAccount , CrunchifyTransaction . TransactionType . WITHDRAW_MONEY , 50 ) ; t . start ( ) ; } // Let's just wait for a second to make sure all thread execution completes. try { Thread . sleep ( 1000 ) ; } catch ( InterruptedException e ) { System . out . println ( e ) ; } // Expected account balance is 5000 System . out . println ( "Final Account Balance: " + crunchifyAccount . getAccountBalance ( ) ) ; } } |
CrunchifyTransaction.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
package com . crunchify . tutorial ; /** * @author Crunchify.com */ class CrunchifyTransaction extends Thread { public static enum TransactionType { DEPOSIT_MONEY ( 1 ) , WITHDRAW_MONEY ( 2 ) ; private TransactionType ( int value ) { } } ; private TransactionType transactionType ; private CrunchifyBankAccount crunchifyAccount ; private double crunchifyAmount ; /* * If transactionType == 1, depositAmount() else if transactionType == 2 withdrawAmount() */ public CrunchifyTransaction ( CrunchifyBankAccount crunchifyAccount , TransactionType transactionType , double crunchifyAmount ) { this . transactionType = transactionType ; this . crunchifyAccount = crunchifyAccount ; this . crunchifyAmount = crunchifyAmount ; } public void run ( ) { switch ( this . transactionType ) { case DEPOSIT_MONEY : depositAmount ( ) ; printBalance ( ) ; break ; case WITHDRAW_MONEY : withdrawAmount ( ) ; printBalance ( ) ; break ; default : System . out . println ( "NOT A VALID TRANSACTION" ) ; } } public void depositAmount ( ) { this . crunchifyAccount . depositAmount ( this . crunchifyAmount ) ; } public void withdrawAmount ( ) { this . crunchifyAccount . withdrawAmount ( crunchifyAmount ) ; } public void printBalance ( ) { System . out . println ( Thread . currentThread ( ) . getName ( ) + " : TransactionType: " + this . transactionType + ", Amount: " + this . crunchifyAmount ) ; System . out . println ( "New Account Balance: " + this . crunchifyAccount . getAccountBalance ( ) ) ; } } |

CrunchifyBankAccount.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
package com . crunchify . tutorial ; /** * @author Crunchify.com */ class CrunchifyBankAccount { private String crunchifyAccountNumber ; private double crunchifyAccountBalance ; public String getAccountNumber ( ) { return crunchifyAccountNumber ; } public double getAccountBalance ( ) { return crunchifyAccountBalance ; } public CrunchifyBankAccount ( String crunchifyAccountNumber ) { this . crunchifyAccountNumber = crunchifyAccountNumber ; } // Make a note of this line -- synchronized keyword added public synchronized boolean depositAmount ( double amount ) { if ( amount < 0 ) { return false ; } else { crunchifyAccountBalance = crunchifyAccountBalance + amount ; return true ; } } // Make a note of this line -- synchronized keyword added public synchronized boolean withdrawAmount ( double amount ) { if ( amount > crunchifyAccountBalance ) { return false ; } else { crunchifyAccountBalance = crunchifyAccountBalance - amount ; return true ; } } } |
請檢查上面的第 24 和 34 行。 保留Synchronized
關鍵字並運行您的程序。 您應該看到正確的結果,如下圖所示。
現在從第 24 行和第 34 行刪除同步關鍵字並運行相同的程序。
您可能需要多次運行此程序才能看到問題。 在 java 中,不能保證你會一直看到 Race 條件。
如果您有企業級應用程序並且您正在談論每秒數百萬筆交易,那麼競爭條件可能會給您的公司帶來災難。
現在的問題是如何避免 Java 應用程序中的競爭條件?
- 如果競態條件是對某些共享內存數據結構的更新,則需要以適當的方式同步對數據結構的訪問和更新。
- 如果競爭條件是在更新數據庫中,則需要重組 SQL 以使用適當粒度級別的事務。
- 在投入生產之前進行負載測試並不是一個壞主意。 更多的負載可能會導致罕見的競爭條件。 最好先修復它,然後再修復它。
- 確保沒有要寫入的全局變量。
- 在 Java 中,每個對像都有一個且只有一個與之關聯的監視器和互斥鎖。 然而,單個監視器有幾個門,每個都由
synchronized
關鍵字指示。 當一個線程通過synchronized
關鍵字時,它有效地鎖定了所有的門。 - 當然,如果一個線程沒有通過
synchronized
關鍵字,它就沒有鎖門,並且其他一些線程隨時可以免費闖入。