Javaマルチスレッド同時実行の例の競合状態に気づきましたか? どのように対処しますか?
公開: 2019-12-11 しばらく前に、プロデューサーコンシューマーの例と、Javaで読み取り/書き込み操作をより適切に処理する方法に関する記事を書きました。 同様に、このチュートリアルでは、競合Race Condition
とThread locking
について説明します。
以下の質問のいずれかがある場合は、適切な場所にいます。
- Javaの競合状態の例
- mutexjavaの例
- マルチスレッド–競合状態とは何ですか?
- 競合状態とクリティカルセクション
- 競合状態とは何ですか?
- 例を使用してJavaで競合状態を処理する方法
Javaで競合状態が発生するのはなぜですか?
Javaの競合状態は、 two or more threads
が共有データをsame time
変更/更新しようとしたときに発生します。
以下のプログラムロジックを見てみましょう。
これは非常に単純な銀行の例で、金額を
100 times
deposit
たりwithdraw
たりします。 $ 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では、競合状態が常に表示される保証はありません。
エンタープライズレベルのアプリケーションがあり、1秒あたり数百万のトランザクションについて話している場合、競合状態は会社に災害を引き起こす可能性があります。
ここで問題となるのは、Javaアプリケーションで競合状態を回避する方法です。
- 競合状態が一部の共有メモリ内データ構造の更新にある場合は、データ構造へのアクセスと更新を適切な方法で同期する必要があります。
- 競合状態がデータベースの更新にある場合は、適切なレベルの粒度でトランザクションを使用するようにSQLを再構築する必要があります。
- 本番環境に移行する前に負荷テストを行うことは悪い考えではありません。 より多くの負荷が発生すると、まれな競合状態が発生する可能性があります。 後で修正するのではなく、前に修正することをお勧めします。
- 書き込むグローバル変数がないことを確認してください。
- Javaでは、すべてのオブジェクトに1つだけのモニターとミューテックスが関連付けられています。 単一のモニターにはいくつかのドアがありますが、それぞれが
synchronized
キーワードで示されています。 スレッドがsynchronized
キーワードを通過すると、すべてのドアが効果的にロックされます。 - もちろん、スレッドが
synchronized
キーワードを通過しない場合、そのスレッドはドアをロックしておらず、他のスレッドはいつでもフリーバージインされます。