1概要
この簡単な記事は、Javaで
synchronized
ブロックを使用するためのイントロです。
簡単に言うと、マルチスレッド環境では、2つ以上のスレッドが同時に可変共有データを更新しようとすると競合状態が発生します。 Javaは、スレッドアクセスを共有データに同期させることによって競合状態を回避するメカニズムを提供します。
synchronized
とマークされたロジックは同期ブロックになります。
2.なぜ同期なのか
合計を計算し、複数のスレッドが
calculate()
メソッドを実行する典型的な競合状態を考えてみましょう。
public class BaeldungSynchronizedMethods {
private int sum = 0;
public void calculate() {
setSum(getSum() + 1);
}
//standard setters and getters
}
それでは簡単なテストを書きましょう。
@Test
public void givenMultiThread__whenNonSyncMethod() {
ExecutorService service = Executors.newFixedThreadPool(3);
BaeldungSynchronizedMethods summation = new BaeldungSynchronizedMethods();
IntStream.range(0, 1000)
.forEach(count -> service.submit(summation::calculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, summation.getSum());
}
単に3つのスレッドプールを持つ
ExecutorService
を使用して
calculate()
を1000回実行しています。
これを連続して実行すると、予想される出力は1000になりますが、
マルチスレッド実行はほぼ毎回失敗します
実際の出力は矛盾しています。
java.lang.AssertionError: expected:<1000> but was:(965)
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
...
この結果はもちろん予想外のことではありません。
競合状態を回避する簡単な方法は、
synchronized
キーワードを使用して操作をスレッドセーフにすることです。
3
synchronized
キーワード
synchronized
キーワードはさまざまなレベルで使用できます。
-
インスタンスメソッド
-
静的メソッド
-
コードブロック
私たちが
synchronized
ブロックを使うとき、内部的にJavaは同期を提供するためにモニターロックまたは組み込みロックとも呼ばれるモニターを使います。
これらのモニタはオブジェクトにバインドされているため、同じオブジェクトのすべての同期ブロックに同時に実行できるスレッドは1つだけです。
3.1. __同期化されたインスタンスメソッド
メソッドを同期させるには、メソッド宣言に
synchronized
キーワードを追加するだけです。
public synchronized void synchronisedCalculate() {
setSum(getSum() + 1);
}
メソッドを同期すると、テストケースが成功し、実際の出力は1000になります。
@Test
public void givenMultiThread__whenMethodSync() {
ExecutorService service = Executors.newFixedThreadPool(3);
SynchronizedMethods method = new SynchronizedMethods();
IntStream.range(0, 1000)
.forEach(count -> service.submit(method::synchronisedCalculate));
service.awaitTermination(1000, TimeUnit.MILLISECONDS);
assertEquals(1000, method.getSum());
}
インスタンスメソッドは、そのメソッドを所有するクラスのインスタンスに対して同期化されています。つまり、クラスのインスタンスごとにこのメソッドを実行できるスレッドは1つだけです。
3.2.
同期化されたStati
c
__メソッド
静的メソッドはインスタンスメソッドと同じように「同期化」されています。
public static synchronized void syncStaticCalculate() {
staticSum = staticSum + 1;
}
これらのメソッドは、クラスに関連付けられた
Class
オブジェクトに対して
synchronized
され、クラスごとにJVMごとに1つの
Class
オブジェクトしか存在しないため、クラスごとに
static
synchronized
メソッド内で実行できるスレッドは1つだけです。
テストしましょう。
@Test
public void givenMultiThread__whenStaticSyncMethod() {
ExecutorService service = Executors.newCachedThreadPool();
IntStream.range(0, 1000)
.forEach(count ->
service.submit(BaeldungSynchronizedMethods::syncStaticCalculate));
service.awaitTermination(100, TimeUnit.MILLISECONDS);
assertEquals(1000, BaeldungSynchronizedMethods.staticSum);
}
3.3. メソッド内の
Synchronized
ブロック
メソッド全体を同期させるのではなく、その中のいくつかの命令だけを同期させたくない場合があります。これは、ブロックに同期を適用することで実現できます。
public void performSynchrinisedTask() {
synchronized (this) {
setCount(getCount()+1);
}
}
変更をテストしましょう。
@Test
public void givenMultiThread__whenBlockSync() {
ExecutorService service = Executors.newFixedThreadPool(3);
BaeldungSynchronizedBlocks synchronizedBlocks = new BaeldungSynchronizedBlocks();
IntStream.range(0, 1000)
.forEach(count ->
service.submit(synchronizedBlocks::performSynchronisedTask));
service.awaitTermination(100, TimeUnit.MILLISECONDS);
assertEquals(1000, synchronizedBlocks.getCount());
}
パラメータ
this
を
synchronized
ブロックに渡したことに注意してください。
これはモニタオブジェクトです。ブロック内のコードはモニタオブジェクトと同期します。簡単に言うと、モニタオブジェクトごとに1つのスレッドだけがそのコードブロック内で実行できます。
メソッドが
staticの場合は、
オブジェクト参照の代わりにクラス名を渡します。そして、クラスはブロックの同期のためのモニターになります。
public static void performStaticSyncTask(){
synchronized (SynchronisedBlocks.class) {
setStaticCount(getStaticCount() + 1);
}
}
static
メソッド内でブロックをテストしましょう。
@Test
public void givenMultiThread__whenStaticSyncBlock() {
ExecutorService service = Executors.newCachedThreadPool();
IntStream.range(0, 1000)
.forEach(count ->
service.submit(BaeldungSynchronizedBlocks::performStaticSyncTask));
service.awaitTermination(100, TimeUnit.MILLISECONDS);
assertEquals(1000, BaeldungSynchronizedBlocks.getStaticCount());
}
5結論
このクイック記事では、スレッド同期を達成するために
synchronized
キーワードを使用するさまざまな方法について説明しました。
また、競合状態がアプリケーションにどのような影響を与える可能性があるのか、同期化がそれを回避するのにどのように役立つかについても調査しました。 Javaでのロックを使用したスレッドセーフについての詳細は、
java.util.concurrent.Locks
article
を参照してください。
このチュートリアルの完全なコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-concurrency[GitHubで利用可能]です。