Javaにおけるスレッド同期のsynchronizedキーワードの使い方

1.synchronizedの機能

synchronized修飾子はメソッドやコードブロックに適用可能で、一度に1つのスレッドしか実行できないように排他制御を実装します。また共有変数に対するメモリ可視性を保証する効果もあります。

2.synchronizedの動作メカニズム

Javaではすべてのオブジェクトがロックオブジェクトとして機能します。このロックは以下の3種類に分類されます:

  • インスタンスメソッド用オブジェクトロック
  • staticメソッド用クラスロック(.classオブジェクト)
  • 明示的なコードブロック用ロック

3.synchronizedの主な使用パターン

synchronizedの主要な使用法は以下の通りです:

  • インスタンスメソッド修飾:インスタンスオブジェクトをロック
  • staticメソッド修飾:クラスオブジェクトをロック
  • コードブロック修飾:明示的に指定されたオブジェクトをロック

4.実装例

以下に具体的な実装例を示します:

public class ConcurrentControlExample {
    
    private static Integer counter = 0;
    
    /**
     * インスタンスメソッドの同期
     */
    public synchronized void method1() {
        System.out.println("インスタンスメソッド実行中: " + this.hashCode());
    }

    /**
     * クラスメソッドの同期
     */
    public synchronized static void method2() {
        System.out.println("staticメソッド実行中: " + ConcurrentControlExample.class.hashCode());
    }

    public void method3() {
        // 明示的なオブジェクトロック
        synchronized(counter) {
            System.out.println("コードブロック実行中: " + counter.hashCode());
        }
    }
    
    public void method4() {
        // クラスロック
        synchronized(Person.class) {
            System.out.println("Personクラスロック: " + Person.class.hashCode());
        }
    }
}

5.さまざまなシナリオでの同期挙動(*面接対策必須*)

5.1 インスタンスメソッドの同期

同じインスタンスに対して複数スレッドがmethod1()を呼び出すと排他制御されますが、異なるインスタンス間では排他されません。

public static void testInstanceMethod() {
    ConcurrentControlExample objA = new ConcurrentControlExample();
    ConcurrentControlExample objB = new ConcurrentControlExample();
    
    Thread t1 = new Thread(() -> {
        System.out.println("スレッド" + Thread.currentThread().getName() + "がobjA.method1()を実行");
        objA.method1();
    });
    
    Thread t2 = new Thread(() -> {
        System.out.println("スレッド" + Thread.currentThread().getName() + "がobjA.method1()を実行");
        objA.method1();
    });
    
    Thread t3 = new Thread(() -> {
        System.out.println("スレッド" + Thread.currentThread().getName() + "がobjB.method1()を実行");
        objB.method1();
    });
    
    t1.start(); t2.start(); t3.start();
}

実行結果:

スレッドThread-0がobjA.method1()を実行
インスタンスメソッド実行中: 123456789
スレッドThread-1がobjA.method1()を実行
インスタンスメソッド実行中: 123456789
スレッドThread-2がobjB.method1()を実行
インスタンスメソッド実行中: 987654321

5.2 staticメソッドの同期

staticメソッドmethod2()はクラスレベルで排他されるため、どのインスタンス経由で呼び出しても同期されます。

public static void testStaticMethod() {
    Thread t1 = new Thread(() -> {
        System.out.println("スレッド" + Thread.currentThread().getName() + "がstaticメソッドを実行");
        ConcurrentControlExample.method2();
    });
    
    Thread t2 = new Thread(() -> {
        System.out.println("スレッド" + Thread.currentThread().getName() + "がstaticメソッドを実行");
        new ConcurrentControlExample().method2();
    });
    
    t1.start(); t2.start();
}

5.3 コードブロックの同期

synchronized(this)を使用したコードブロックはインスタンスロックを取得し、同期効果を持ちます。

public void operation1() {
    synchronized(this) {
        System.out.println("オペレーション1実行中: " + this.hashCode());
    }
}

public void operation2() {
    synchronized(this) {
        System.out.println("オペレーション2実行中: " + this.hashCode());
    }
}

5.4 外部オブジェクトのロック

非static変数やstatic変数をロックオブジェクトとして使用した場合の挙動を比較します。

private Resource localResource = new Resource();
private static Resource sharedResource = new Resource();

public void processLocal() {
    synchronized(localResource) {
        System.out.println("ローカルリソースロック: " + localResource.hashCode());
    }
}

public void processShared() {
    synchronized(sharedResource) {
        System.out.println("共有リソースロック: " + sharedResource.hashCode());
    }
}

5.5 クラスロックの特殊ケース

Person.classのようなクラスオブジェクトをロックすると、他のインスタンス間でも同期が発生します。

要点まとめ

  1. 同期の成立条件はロックオブジェクトの同一性に依存
  2. インスタンスメソッドはthisオブジェクトをロック
  3. staticメソッドはクラスオブジェクトをロック
  4. コードブロックは明示的に指定されたオブジェクトをロック
  5. ロックオブジェクトの選択が同期範囲を決定

※本記事はsynchronizedの基本的な使用方法を解説しました。次回は内部実装とパフォーマンス最適化について詳しく説明します。

タグ: Javaスレッド同期 オブジェクトロック クラスロック メソッド同期 コードブロック同期

6月5日 20:34 投稿