読者です 読者をやめる 読者になる 読者になる

Javaでsynchronizedの排他制御を試す

Javaでsynchronizedメソッドで排他制御を試したメモです。

Javaでsynchronizedメソッドでのスレッドの排他制御を確認してみました。

synchronizedメソッド

synchronizedをメソッドに付与すると、メソッド実行時にそのオブジェクトのロックを取得できます。
これはsynchronizedブロックでそのオブジェクトを指定したものと同義です。
ロックを取得している間、他のスレッドはそのオブジェクトのロックを取得できません。
そのため同時に1つのスレッドからしか実行できません。
メソッド終了時にそのオブジェクトのロックを開放します。

確認用クラス

MyClassクラスを定義し、下記メソッドを定義しました。
インスタンスメソッド
・synchronized インスタンスメソッドA
・synchronized インスタンスメソッドB
・クラスメソッド
・synchronized クラスメソッドA
・synchronized クラスメソッドB

MyClass.java

public class MyClass {
    public void myInstanceMethod() {
        System.out.println(Thread.currentThread().getName() + ": instanceMethod start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": instanceMethod end");
    }

    public synchronized void syncInstanceMethodA() {
        System.out.println(Thread.currentThread().getName() + ": syncInstanceMethodA start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": syncInstanceMethodA end");
    }

    public synchronized void syncInstanceMethodB() {
        System.out.println(Thread.currentThread().getName() + ": syncInstanceMethodB start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": syncInstanceMethodB end");
    }

    public static void myStaticMethod() {
        System.out.println(Thread.currentThread().getName() + ": staticMethod start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": staticMethod end");
    }

    public static synchronized void syncStaticMethodA() {
        System.out.println(Thread.currentThread().getName() + ": syncStaticMethodA start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": syncStaticMethodA end");
    }

    public static synchronized void syncStaticMethodB() {
        System.out.println(Thread.currentThread().getName() + ": syncStaticMethodB start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + ": syncStaticMethodB end");
    }
}

確認

マルチスレッドのテストは難しく、完全な安全性を保証することは出来ないですが、
動作確認用の簡易テストで確認しました。

インスタンスメソッドとインスタンスメソッド

synchronizedでないインスタンスメソッドは複数のスレッドから同時実行可能。
ロックを取得しないので、複数スレッドから実行可能です。

@Test
public void 同時実行可_インスタンスメソッド() throws InterruptedException {
    MyClass clazz = new MyClass();
    Runnable task = clazz::myInstanceMethod;

    new Thread(task).start();
    new Thread(task).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: instanceMethod start
Thread-1: instanceMethod start
Thread-1: instanceMethod end
Thread-0: instanceMethod end
インスタンスメソッドとsynchronizedインスタンスメソッド

synchronizedでないインスタンスメソッドとsynchronizedインスタンスメソッドは
複数のスレッドから同時実行可能。
synchronizedでないインスタンスメソッドはロックを取得しないので、
他のsynchronizedメソッドのロックにかかわらず実行可能です。

@Test
public void 同時実行可_インスタンスメソッドとsynchronizedメソッド() throws InterruptedException {
    MyClass clazz = new MyClass();
    Runnable taskNonSync = clazz::myInstanceMethod;
    Runnable taskSync = clazz::syncInstanceMethodA;

    new Thread(taskNonSync).start();
    new Thread(taskSync).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: instanceMethod start
Thread-1: syncInstanceMethodA start
Thread-1: syncInstanceMethodA end
Thread-0: instanceMethod end
synchronizedインスタンスメソッドとsynchronizedインスタンスメソッド

synchronizedインスタンスメソッドは複数のスレッドから同時実行不可。
synchronizedはそのインスタンスのロックを取得するため、同時実行できません。

@Test
public void 同時実行不可_synchronizedメソッド() throws InterruptedException {
    MyClass clazz = new MyClass();
    Runnable taskSync = clazz::syncInstanceMethodA;

    new Thread(taskSync).start();
    new Thread(taskSync).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncInstanceMethodA start
Thread-0: syncInstanceMethodA end
Thread-1: syncInstanceMethodA start
Thread-1: syncInstanceMethodA end
異なるsynchronizedインスタンスメソッド

異なるsynchronizedインスタンスメソッドは複数のスレッドから同時実行不可。
synchronizedはそのインスタンスのロックを取得するため、異なるメソッドでも同時実行できません。

@Test
public void 同時実行不可_異なるsynchronizedメソッド() throws InterruptedException {
    MyClass clazz = new MyClass();
    Runnable taskSyncA = clazz::syncInstanceMethodA;
    Runnable taskSyncB = clazz::syncInstanceMethodB;

    new Thread(taskSyncA).start();
    new Thread(taskSyncB).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncInstanceMethodA start
Thread-0: syncInstanceMethodA end
Thread-1: syncInstanceMethodB start
Thread-1: syncInstanceMethodB end
異なるインスタンスのsynchronizedインスタンスメソッド

異なるインスタンスのsynchronizedインスタンスメソッドは複数のスレッドから同時実行可能。
synchronizedはそのインスタンスのロックを取得するため、インスタンスが異なればロックも異なるため
同時に実行できます。

@Test
public void 同時実行可_異なるインスタンスのsynchronizedメソッド() throws InterruptedException {
    MyClass clazz1 = new MyClass();
    MyClass clazz2 = new MyClass();
    Runnable taskClazz1 = clazz1::syncInstanceMethodA;
    Runnable taskClazz2 = clazz2::syncInstanceMethodA;

    new Thread(taskClazz1).start();
    new Thread(taskClazz2).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncInstanceMethodA start
Thread-1: syncInstanceMethodA start
Thread-1: syncInstanceMethodA end
Thread-0: syncInstanceMethodA end
異なるインスタンスの異なるsynchronizedインスタンスメソッド

異なるインスタンスの異なるsynchronizedインスタンスメソッドは複数のスレッドから同時実行可能。
synchronizedはそのインスタンスのロックを取得するため、インスタンスが異なればロックも異なるため
同時に実行できます。

@Test
public void 同時実行可_異なるインスタンスの異なるsynchronizedメソッド() throws InterruptedException {
    MyClass clazz1 = new MyClass();
    MyClass clazz2 = new MyClass();
    Runnable taskClazz1 = clazz1::syncInstanceMethodA;
    Runnable taskClazz2 = clazz2::syncInstanceMethodB;

    new Thread(taskClazz1).start();
    new Thread(taskClazz2).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncInstanceMethodA start
Thread-1: syncInstanceMethodB start
Thread-0: syncInstanceMethodA end
Thread-1: syncInstanceMethodB end
synchronizedインスタンスメソッドとクラスメソッド

synchronizedインスタンスメソッドとクラスメソッドは複数のスレッドから同時実行可能。
クラスメソッドはsynchronizedでないため同時に実行できます。

@Test
public void 同時実行可_synchronizedメソッドとstaticメソッド() throws InterruptedException {
    MyClass clazz = new MyClass();
    Runnable taskSync = clazz::syncInstanceMethodA;
    Runnable taskStatic = MyClass::myStaticMethod;

    new Thread(taskSync).start();
    new Thread(taskStatic).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncInstanceMethodA start
Thread-1: staticMethod start
Thread-0: syncInstanceMethodA end
Thread-1: staticMethod end
synchronizedインスタンスメソッドとsynchronizedクラスメソッド

synchronizedインスタンスメソッドとsynchronizedクラスメソッドは複数のスレッドから同時実行可能。
インスタンスのロックとクラスのロックは異なるため、同時に実行できます。

@Test
public void 同時実行可_synchronizedメソッドとstaticSynchronizedメソッド() throws InterruptedException {
    MyClass clazz = new MyClass();
    Runnable taskSync = clazz::syncInstanceMethodA;
    Runnable taskStaticSync = MyClass::syncStaticMethodA;

    new Thread(taskSync).start();
    new Thread(taskStaticSync).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncInstanceMethodA start
Thread-1: syncStaticMethodA start
Thread-0: syncInstanceMethodA end
Thread-1: syncStaticMethodA end
synchronizedクラスメソッドとsynchronizedクラスメソッド

synchronizedクラスメソッドとsynchronizedクラスメソッドは複数のスレッドから同時実行不可。
synchronizedでそのクラスのロックを取得するため、同時実行できません。

@Test
public void 同時実行不可_staticSynchronizedメソッド() throws InterruptedException {
    Runnable taskStaticSyncA = MyClass::syncStaticMethodA;

    new Thread(taskStaticSyncA).start();
    new Thread(taskStaticSyncA).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncStaticMethodA start
Thread-0: syncStaticMethodA end
Thread-1: syncStaticMethodA start
Thread-1: syncStaticMethodA end
異なるsynchronizedクラスメソッド

異なるsynchronizedクラスメソッドは複数のスレッドから同時実行不可。
異なるsynchronizedクラスメソッドでも、同じクラスのロックを取得するため、同時実行できません。

@Test
public void 同時実行不可_異なるstaticSynchronizedメソッド() throws InterruptedException {
    Runnable taskStaticSyncA = MyClass::syncStaticMethodA;
    Runnable taskStaticSyncB = MyClass::syncStaticMethodB;

    new Thread(taskStaticSyncA).start();
    new Thread(taskStaticSyncB).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncStaticMethodA start
Thread-0: syncStaticMethodA end
Thread-1: syncStaticMethodB start
Thread-1: syncStaticMethodB end
異なるインスタンスから異なるsynchronizedクラスメソッド

異なるインスタンス経由で異なるsynchronizedクラスメソッドは複数のスレッドから同時実行不可。
異なるインスタンス経由でもsynchronizedで同じクラスのロックを取得するため、同時実行できません。

@Test
public void 同時実行不可_異なるインスタンスから異なるstaticSynchronizedメソッド() throws InterruptedException {
    MyClass clazz1 = new MyClass();
    MyClass clazz2 = new MyClass();
    Runnable taskStaticSync1 = () -> {
        clazz1.syncStaticMethodA();
    };
    Runnable taskStaticSync2 = () -> {
        clazz2.syncStaticMethodB();
    };

    new Thread(taskStaticSync1).start();
    new Thread(taskStaticSync2).start();
    Thread.sleep(5000);
}

テスト結果

Thread-0: syncStaticMethodA start
Thread-0: syncStaticMethodA end
Thread-1: syncStaticMethodB start
Thread-1: syncStaticMethodB end

テストコードは下記にあげました。
github.com

終わり。