synchronizedは、クリティカルセクションへのスレッドセーフなアクセスを実現するためのキーワードです。それはメソッド(例えば、public synchronized void foo())やコードブロック(synchronized(obj) { ... })のいずれかに使用できます。スレッドがsynchronizedブロックに入ると、そのオブジェクトのモニター(ロック)を取得します。モニターが使用されている間、他のスレッドは同じオブジェクトをモニターとして使用する別のsynchronizedブロックに入ることができません。
特徴:
thisや静的オブジェクト(例えば、クラス)が同期対象として使用でき、任意のオブジェクトでも可能です。public class Counter { private int count; private final Object lock = new Object(); // プライベートオブジェクト public void increment() { synchronized(lock) { count++; } } }
なぜプライベートなロックを使用する方が良いのか? 公開されたオブジェクト(例えば、thisや共有可能な文字列)に対して同期を行うと、外部コードがこのモニターを奪う可能性があり、それがデッドロックや予期しない動作を引き起こすことになります。
質問: 固定値を含むString型のオブジェクトで同期するとどうなりますか?
答え: Javaの文字列はインターンされているため(同じリテラルに対して同じオブジェクトが使用されます)、例えばsynchronized("lock")で同期した場合、同じリテラルで同期している他のコードと衝突する可能性があり、プログラムの全く異なる部分間で予期しないブロックが発生する可能性があります。
synchronized("LOCK") { ... }
物語
多スレッドの取引システムで公開されたオブジェクトを使用して同期を行った結果、外部コードがロックを取得できてしまい、異なるモジュール間でのスレッド間デッドロックが発生し、取引所での停止が生じました。
物語
若手開発者が文字列リテラルを用いてコレクションへのアクセスを同期した。他のコードも同じ値の文字列で同期しており、これによりこれらのスレッドが互いに待機することになり、ビジネスロジックが著しく遅くなりました。
物語
毎回新しいオブジェクトを選択して同期した結果:
synchronized(new Object()) { ... }。その結果、同期は全く機能せず、異なるスレッドがデータに同時アクセスできる状況が生じました。これは負荷テストでのみ発見されました。