Java でスレッド同期制御にキーワード synchronized を使用すると、主要なリソースへの順次アクセスを実現し、複数のスレッドの同時実行によって引き起こされるデータの不整合などの問題を回避できます。同期の原理は、オブジェクト モニター (ロック) です。モニターを取得したスレッドのみが実行を継続できます。それ以外の場合、スレッドはモニターを取得するまで待機します。 Java の各オブジェクトまたはクラスにはロックが関連付けられています。オブジェクトの場合、監視されるのはオブジェクトのインスタンス変数です。クラスの場合、監視されるのはクラス変数です (クラス自体はクラス Class のオブジェクトです)。したがって、クラスに関連付けられたロックもオブジェクト ロックです)。 synchronized キーワードを使用するには、synchronized メソッドと synchronized ブロックの 2 つの方法があります。両方の監視領域は、導入されたオブジェクトに関連付けられています。この監視領域に到着すると、JVM は参照オブジェクトをロックし、終了するときに参照オブジェクトのロックを解放します (例外終了がある場合、JVM はロックを解放します)。 。オブジェクトのロックは、JVM の内部メカニズムです。同期メソッドまたは同期ブロックを記述するだけで、JVM は自動的にロックを取得または解放します。
例 1
まず、最初の例を見てみましょう。Java では、同じオブジェクトの 1 つのクリティカル セクションのみに同時にアクセスできます (すべて非静的同期メソッドです)。 a 銀行口座の模擬申し込みで、チャージや残高からの引き落としが可能です。このプログラムは、addAmount() メソッドを 100 回呼び出してアカウントにリチャージし、毎回 1000 をデポジットします。次に、subtractAmount() メソッドを 100 回呼び出して、毎回 1000 を差し引くことでアカウントの残高を差し引きます。初期残高と同じ synchronized キーワードによって平等が達成されます。
共有データの同時アクセスの問題を確認したい場合は、addAmount() メソッド宣言とsubtractAmount() メソッド宣言内の synchronized キーワードを削除するだけです。 synchronized キーワードがないと、出力される残高値が不一致になります。このプログラムを複数回実行すると、異なる結果が得られます。 JVM はスレッドの実行順序を保証しないため、スレッドは実行されるたびに異なる順序でアカウント残高を読み取り、変更し、その結果、異なる最終結果が生じます。
オブジェクトのメソッドは synchronized キーワードを使用して宣言され、1 つのスレッドによってのみアクセスできます。スレッド A が同期メソッド syncMethodA() を実行しており、スレッド B がこのオブジェクトの他の同期メソッド syncMethodB() を実行したい場合、スレッド A がアクセスを完了するまでスレッド B はブロックされます。ただし、スレッド B が同じクラスの異なるオブジェクトにアクセスする場合、どちらのスレッドもブロックされません。
例 2
同じオブジェクト上の静的同期メソッドと非静的同期メソッドが同時に複数のスレッドからアクセスできるという問題を示します。
package concurrency; public class Main8 { public static void main(String[] args) { Account account = new Account(); account.setBalance(1000); Company company = new Company(account); Thread companyThread = new Thread(company); Bank bank = new Bank(account); Thread bankThread = new Thread(bank); System.out.printf("Account : Initial Balance: %f\n", account.getBalance()); companyThread.start(); bankThread.start(); try { //join()方法等待这两个线程运行完成 companyThread.join(); bankThread.join(); System.out.printf("Account : Final Balance: %f\n", account.getBalance()); } catch (InterruptedException e) { e.printStackTrace(); } } }
/*帐户*/ class Account{ private double balance; /*将传入的数据加到余额balance中*/ public synchronized void addAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp += amount; balance = tmp; } /*将传入的数据从余额balance中扣除*/ public synchronized void subtractAmount(double amount){ double tmp = balance; try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } tmp -= amount; balance = tmp; } public double getBalance() { return balance; } public void setBalance(double balance) { this.balance = balance; } }
/*银行*/ class Bank implements Runnable{ private Account account; public Bank(Account account){ this.account = account; } @Override public void run() { for (int i = 0; i < 100; i++) { account.subtractAmount(1000); } } }
static キーワードを追加して、前の例の残高を変更しました。 addAmount() メソッドは、static キーワードを使用して変更することもできます。実行結果は自分でテストできます。実行ごとに異なる結果が得られます。
概要:
synchronized はソフトウェア (JVM) を通じて実装されており、JDK5 以降の Lock を使用した場合でも、依然として広く使用されています。
同期は実際には不公平です。新しいスレッドはすぐにモニターを取得し、待機領域で長時間待機していたスレッドは再び待機する可能性があります。ただし、このプリエンプション方法はスタベーションを防ぐことができます。
同期のみのロックは 1 つの条件 (ロックを取得するかどうか) のみに関連付けられているため、柔軟性がありません。その後、条件とロックを組み合わせることでこの問題が解決されました。
複数のスレッドがロックをめぐって競合する場合、ロックを取得していない残りのスレッドは、中断することなくロックの取得を試行し続けることしかできません。同時実行性が高いとパフォーマンスの低下につながります。 ReentrantLock の lockInterruptibly() メソッドは、割り込みへの応答を優先できます。 スレッドの待機時間が長すぎると、スレッド自体に割り込みが発生する可能性があり、ReentrantLock が割り込みに応答して、スレッドの待機を継続させなくなります。この仕組みにより、ReentrantLockを使用する場合、同期時のようなデッドロックが発生しません。
同期変更メソッドを使用してスレッドを同期する Java のその他の例については、PHP 中国語 Web サイトに注目してください。