この記事では、主に、Javaマルチスレッドのスレッド安全性の問題を要約するために、以前の2つのマルチスレッド記事に続きます。
1.典型的なJavaスレッドの安全性の例
public class threadtest {public static void main(string [] args){account account = new Account( "123456"、1000); DrawMoneyRunnable DrawMoneyRunnable = new DrawMoneyRunnable(アカウント、700);スレッドMythRead1 = newスレッド(drawMoneyRunnable);スレッドmythread2 = newスレッド(drawMoneyRunnable); mythread1.start(); mythread2.start(); }} class DrawMoneyRunnable runnable {privateアカウント。プライベートダブルドローマウント; public drawMoneyRunnable(account councount、double drawamount){super(); this.account = account; this.drawamount = drawamount; } public void run(){if(account.getBalance()> = drawAmount){// 1 System.out.println( "撤回が成功した、お金の撤回は次のとおりです。 double balance = account.getbalance() - drawamount; account.setbalance(balance); system.out.println( "残高は:" + balance); }}} classアカウント{private string courcountno;プライベートダブルバランス。 publicアカウント(){} publicアカウント(String Accountno、double balance){this.accountno = courcountno; this.balance = balance; } public string getAccountNo(){return Accountno; } public void setAccountno(String Accountno){this.accountno = courcountno; } public double getBalance(){return balance; } public void setbalance(double balance){this.balance = balance; }}上記の例は理解しやすいです。残高は1,000の銀行カードがあります。このプログラムは、あなたとあなたの妻が同時にATMでお金を引き出しているシーンをシミュレートします。このプログラムを複数回実行すると、複数の異なる組み合わせで出力結果が得られる場合があります。可能な出力の1つは次のとおりです。
1お金の撤回は成功し、お金の撤回は700.0です
2バランスは次のとおりです。300.0
3お金の撤回は成功し、お金の撤回は700.0です
4残高は次のとおりです。 -400.0
言い換えれば、残高はわずか1,000の銀行カードの場合、合計1,400を引き出すことができます。これは明らかに問題です。
分析後、問題は、Javaマルチスレッド環境での実行の不確実性にあります。 CPUは、準備が整った状態で複数のスレッドをランダムに切り替えることができるため、次の状況が発生する可能性が非常に高くなります。Thread1が// 1のコードを実行すると、判断条件は真です。現時点では、CPUはthread2に切り替え、// 1のコードを実行し、それがまだ真実であることがわかります。次に、thread2が実行され、thread1に切り替えて、実行が完了します。この時点で、上記の結果が表示されます。
したがって、スレッドの安全性の問題に関しては、実際には、マルチスレッド環境で共有リソースにアクセスすることで、この共有リソースに矛盾を引き起こす可能性があることを意味します。したがって、スレッドの安全性の問題を回避するには、マルチスレッド環境でのこの共有リソースへの同時アクセスを避ける必要があります。
2。同期方法
同期されたキーワードの変更は、共有リソースにアクセスするためのメソッド定義に追加され、同期メソッドと呼ばれるこの方法を作成します。この方法がロックされており、そのロックされたオブジェクトは、現在の方法がある場所であるオブジェクト自体であることを簡単に理解できます。マルチスレッド環境では、このメソッドを実行するときは、まずこの同期ロックを取得する必要があります(最大で1つのスレッドのみを取得できます)。スレッドがこの同期メソッドを実行した場合にのみ、ロックオブジェクトがリリースされ、他のスレッドはこの同期ロックを取得できます。
上記の例では、共有リソースはアカウントオブジェクトであり、同期方法を使用する場合、スレッドの安全性の問題を解決できます。 run()メソッドの前に、Synshronizedキーワードを追加するだけです。
public synchronized void run(){// ....}3.コードブロックを同期します
上記で分析したように、スレッドの安全性の問題を解決するには、共有リソースへのアクセスの不確実性を制限する必要があります。同期法を使用する場合、メソッド本体全体が同期実行状態になり、同期範囲が発生する可能性があります。したがって、別の同期方法 - 同期コードブロック - は、同期が必要なコードに対して直接解決できます。
同期コードブロックの形式は次のとおりです。
同期(OBJ){// ...}その中でも、OBJはロックオブジェクトであるため、ロックするオブジェクトを選択することが重要です。一般的に、この共有リソースオブジェクトはロックオブジェクトとして選択されます。
上記の例のように、アカウントオブジェクトをロックオブジェクトとして使用することをお勧めします。 (もちろん、作成スレッドは実行可能な方法を使用するため、これを選択することも可能です。スレッドメソッドを直接継承するスレッドである場合、このオブジェクトを同期ロックとして使用すると、実際には異なるオブジェクトであるため、実際には役割を果たしません。したがって、同期ロックを選択するときは特に注意する必要があります...)
4.ロックオブジェクト同期ロック
上記のように、同期ロックオブジェクトの選択に非常に注意する必要があるため、簡単な解決策はありますか?共有リソースからの同期ロックオブジェクトのデカップリングを促進すると同時に、スレッドの安全性の問題をうまく解決できます。
ロックオブジェクトの同期ロックを使用すると、この問題を簡単に解決できます。注意すべき唯一のことは、ロックオブジェクトがリソースオブジェクトと1対1の関係を持つ必要があることです。ロックオブジェクトの同期ロックの一般的な形式は次のとおりです。
クラスx {//ロック同期ロックを定義するオブジェクトを表示します。これは、共有リソースプライベートファイナルロックロック= new ReentrantLock()と1対1の関係があります。 public void m(){// lock lock.lock(); // ...スレッドセーフ同期を必要とするコード//ロックロックロック.Unlock()をリリースします。 }}5.wait()/notify()/notifyall()スレッド通信
これらの3つの方法は、ブログ投稿「Java Summaryシリーズ:Java.lang.Object」に記載されています。これらの3つの方法は主にマルチスレッドで使用されていますが、実際にはオブジェクトクラスのローカルメソッドです。したがって、理論的には、すべてのオブジェクトオブジェクトは、これら3つの方法の主要なトーンとして使用できます。実際のマルチスレッドプログラミングでは、ロックオブジェクトを同期してこれらの3つの方法をチューニングすることによってのみ、複数のスレッド間で通信をスレッドすることができます。
wait():現在のスレッドが待機し、待機状態を入力させます。別のスレッドが同期ロックオブジェクトのnotify()またはnotifyall()メソッドを呼び出すまで、スレッドを目覚めさせます。
notify():この同期ロックオブジェクトを待っている単一のスレッドを起動します。複数のスレッドがこの同期ロックオブジェクトを待っている場合、ウェイクアップ操作のためにスレッドの1つが選択されます。現在のスレッドが同期ロックオブジェクトのロックを放棄した場合にのみ、目覚めたスレッドを実行できます。
notifyall():この同期ロックオブジェクトを待っているすべてのスレッドを起動します。現在のスレッドが同期ロックオブジェクトのロックを放棄した場合にのみ、目覚めたスレッドを実行できます。
パッケージcom.qqyumidi; public class threadtest {public static void main(string [] args){account account = new Account( "123456"、0);スレッドdrawmoneythread = new DrawMoneyThread( "Get Money Thread"、Account、700); Thread DeposeMoneyThread = new DeposeMoneyThread( "Save Money Sthread"、Account、700); drawmoneythread.start(); deposemoneythread.start(); }} class drawmoneythread extends thread {privateアカウント;プライベートダブル量; public drawmoneythread(string threadname、accountアカウント、二重の金額){super(threadname); this.account = account; this.Amount =額; } public void run(){for(int i = 0; i <100; i ++){account.draw(ant、i); }}} class deposemoneythread extends thread {privateアカウント;プライベートダブル量; public dositmoneythread(string threadname、accountアカウント、二重の金額){super(threadname); this.account = account; this.Amount =額; } public void run(){for(int i = 0; i <100; i ++){account.deposit(ant、i); }}} classアカウント{private string courcountno;プライベートダブルバランス。 //アカウントにすでにデポジットがあるかどうかを特定しますプライベートブールフラグ= false; publicアカウント(){} publicアカウント(String Accountno、double balance){this.accountno = courcountno; this.balance = balance; } public string getAccountNo(){return Accountno; } public void setAccountno(String Accountno){this.accountno = courcountno; } public double getBalance(){return balance; } public void setbalance(double balance){this.balance = balance; } / ** *お金を節約 * * @param diposemount * / public synchronized void dopite(double positamount、int i){//アカウント内の誰かがすでにお金を節約しているので、{system.out.println(thread.currentthread() + "待って(); // 1 System.out.println(thread.currentthread()。getName() + "formed wait操作" + "-i =" + i); } catch(arturnedexception e){e.printstacktrace(); }} else {// System.out.println(thread.currentThread()。getName() + "detween:" + dispodamount + "-i =" + i); SetBalance(Balance + DeposeMount); flag = true; //他のスレッドをウェイクアップnotifyall(); // 2 try {thread.sleep(3000); } catch(arturnedexception e){e.printstacktrace(); } system.out.println(thread.currentThread()。getName() + " - save money--実行が完了します" + " - i =" + i); }} / ** * Money * * @param Drawamount * / public Synchronized void Draw(double drawamount、int i){//アカウント内の誰もまだ節約していないので、{system.out.println(thread.currentthread()をexectname() "私);待って(); system.out.println(thread.currentthread()。getName() + "execute wait operation" + "execute wait operation" + " - i =" + i); } catch(arturnedexception e){e.printstacktrace(); }} else {// Money System.out.println(thread.currentThread()。getName() + "Money:" + DrawAmount + "-i =" + i); setBalance(getBalance() - drawAmount); flag = false; //他のスレッドをウェイクアップnotifyall(); system.out.println(thread.currentthread()。getName() + " - extlet money-- executionが完了します" + " - i =" + i); // 3}}}上記の例は、wait()/notify()/notifyall()の使用法を示しています。いくつかの出力結果は次のとおりです。
マネーの引き出しスレッドは、待機操作の実行を開始し、待機操作を実行します - i = 0
スレッドデポジットの保存:700.0 -i = 0
お金を節約するためのお金を節約するお金 - 解釈 - i = 0
お金の節約スレッドは待機操作を実行する必要があります - i = 1
マネーの引き出しスレッドは待機操作と待機操作を実行します - i = 0
マネースレッド引き出しお金:700.0 -i = 1
お金の引き出しスレッド - withdrawal - execution-- i = 1
お金を引き出すためのスレッドは、待機操作の実行を開始し、待機操作を実行する必要があります - i = 2
お金を節約するスレッドは待機操作を実行します - i = 1
スレッドデポジットの保存:700.0 -i = 2
お金を節約するためのお金を節約するお金 - 解釈 - i = 2
引き出しスレッドは待機操作を実行し、待機操作を実行します - i = 2
お金のスレッドが撤退したお金:700.0 -i = 3
マネーの引き出しスレッド - withdrawal - execution-I = 3
お金を引き出すスレッドは、待機操作を実行し、待機操作を実行する必要があります - i = 4
スレッドデポジットの保存:700.0 -i = 3
お金を節約するためのお金を節約するお金 - 解釈 - i = 3
お金の節約スレッドは待機操作を実行する必要があります - i = 4
マネー離脱スレッドは待機操作と待機操作を実行します - i = 4
お金のスレッドが撤退したお金:700.0 -i = 5
お金の引き出しスレッド - withdrawal - execution-- i = 5
お金を引き出すスレッドは、待機操作の実行を開始し、待機操作を実行する必要があります - i = 6
お金の節約スレッドは待機操作を実行します - i = 4
スレッドデポジットの保存:700.0 -i = 5
お金を節約するためのお金を節約するお金 - 解釈 - i = 5
お金の節約スレッドは待機操作を実行する必要があります - i = 6
マネー離脱スレッドは待機操作と待機操作を実行します - i = 6
撤退したマネースレッドは、撤退したお金:700.0 -i = 7
お金の引き出しスレッド - withdrawal - execution-- i = 7
マネーの引き出しスレッドは、待機操作の実行を開始し、待機操作を実行します - I = 8
お金を節約するスレッドは待機操作を実行します - i = 6
スレッドデポジットの保存:700.0 -i = 7
したがって、次のポイントに注意を払う必要があります。
1。wait()メソッドが実行された後、現在のスレッドはすぐに待機ブロッキング状態に入り、後続のコードは実行されません。
2。notify()/notifyall()メソッドが実行された後、この同期ロックオブジェクトのスレッドオブジェクト(any-notify()/all-notifyall())が目覚めます。ただし、同期ロックオブジェクトは現時点ではリリースされません。つまり、notify()/notifyall()の背後にコードがある場合、引き続き進みます。現在のスレッドが実行された場合にのみ、同期ロックオブジェクトがリリースされます。
3. notify()/notifyall()が実行された後、右側にSleep()メソッドがある場合、現在のスレッドはブロッキング状態を入力しますが、同期オブジェクトロックはリリースされず、それ自体で保持されます。その後、スレッドは一定の期間、次の2つの後に実行され続けます。
4。Wait()/notify()/nitifyall()は、異なるオブジェクトロックに基づいて通信またはスレッド間のコラボレーションを完了します。したがって、それが別の同期オブジェクトロックである場合、それはその意味を失います。同時に、同期オブジェクトロックは、共有リソースオブジェクトと1対1の対応を維持するのに最適です。
5.待機スレッドが目覚めて実行すると、前回実行されたWait()メソッドコードは引き続き実行されます。
もちろん、上記の例は比較的単純で、wait()/notify()/noitifyall()メソッドを使用するだけですが、本質的には、すでに単純なプロデューサー - 消費者モデルです。
一連の記事:
Javaマルチスレッドインスタンスの説明(i)
Javaマルチスレッドインスタンスの詳細な説明(ii)
Javaマルチスレッドインスタンスの詳細な説明(iii)