Java Gold SE11対策:排他制御と synchronized の基本と深掘り

アイキャッチ画像

排他制御って何?

マルチスレッドで処理を並行実行すると、同じ変数やリソースに複数スレッドが同時にアクセスしてしまうことがあります。

その結果――
「誰かが書いてる途中の値を、別のスレッドが読み取ってしまう」
「同時に書き込みが走って、値が壊れる」
…といった「データの競合(レースコンディション)」が発生します。

この問題を防ぐには、同時に1つのスレッドだけが特定の処理に入れるようにする=排他制御(Mutual Exclusion)が必要です。

Javaの排他制御:synchronized の使い方

方法①:同期メソッド

public synchronized void increment() {

    count++;

}

  • このメソッドは、このオブジェクト(this)に対してロックをかける
  • 同時に呼ばれても、1スレッドずつしか実行されない

方法②:明示的にロック対象を指定(同期ブロック)

もっと細かく制御したいときは、synchronized(ロック対象) を使って、メソッドの一部だけロックできます。

public class Counter {

    private int count = 0;

    private final Object lock = new Object(); // 🔑ロック専用オブジェクト

    public void increment() {

        doSomething(); // 🔓ロック不要な処理

        synchronized (lock) { // 🔒ここだけ排他制御

            count++;

        }

        doSomethingElse(); // 🔓ロック不要な処理

    }

    public int getCount() {

        return count;

    }

}

lockって何?なぜ new Object()

synchronized(lock) の lock には、Object型のインスタンスが必要です。

理由は「Objectにはロック(モニター)機能がある」から!

  • Javaの synchronized(obj) は「objのモニター(内部ロック)」を使う
  • だから Object 型である必要がある(String や Integer も technically OK)

しかし、new Object() が一番安全です。

  • String や Integer は使い回されやすく、ロックの衝突リスクあり
  • new Object() は完全に新規の、誰とも共有されないインスタンス

private final の意味は?

private final Object lock = new Object();

修飾子意味必須?
private他のクラスから見えなくする推奨
final途中で他の値を代入できないようにする強く推奨

これによって、

  • ロックが意図せず外部から変更されない
  • 予期せぬロックの解除・再代入が防げる

つまり、「ロック専用オブジェクトとして最も安全な書き方」が:

private final Object lock = new Object();

synchronized はメソッドじゃない!構文です

synchronized (lock) {

    // 処理

}

これはメソッドではなく、Javaのキーワード+構文

  • synchronized(…) { … } は「そのオブジェクトのロックを取得してから実行する」という構文
  • コンパイル時に特別なバイトコードに変換される

if文のifとかと同じたぐい!

まとめ

項目ポイント
synchronized排他制御のためのキーワード。メソッド修飾子にも、構文にも使える
synchronized (lock)lock オブジェクトのモニターを使って処理を排他制御
ロックに使うオブジェクト基本は new Object()。使い回しされにくく、安全
private finalロック専用にし、他の場所から書き換えられないようにする