Javaでsyncronizedを試す
スレッドまわりの勉強のために以下の本を読んでいます。(第14章:スレッド)
- 作者: ケンアーノルド,デビッドホームズ,ジェームズゴスリン,Ken Arnold,David Holmes,James Gosling,柴田芳樹
- 出版社/メーカー: 東京電機大学出版局
- 発売日: 2014/05/10
- メディア: 単行本
- この商品を含むブログ (4件) を見る
syncronized
についてスレッドの排他制御の挙動を確認してみようと思います。
syncronized
syncronized
で排他制御する方法として大きく以下の2つの方法があります。
- Synchronizedメソッド
- Synchronized文
それぞれのsyncronizedの挙動を確認します。
syncronizedメソッド
1つのスレッドがオブジェクトに対してsyncronizedメソッドを呼び出すと、オブジェクトのロックを取得してメソッドを実行します。
メソッド終了時にロックを開放します。
ロックはスレッド単位になります。
同じオブジェクトに対して、syncronizedメソッドを呼び出した他のスレッドは、ロックを取得できるまで待機します。
確認してみる
実験用メソッドのコード
package sample;
public class MyClass {
public void myInstanceMethod() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
public synchronized void syncInstanceMethodA() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
public synchronized void syncInstanceMethodB() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
public static void myStaticMethod() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
public static synchronized void syncStaticMethodA() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
public static synchronized void syncStaticMethodB() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void sleep() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果サマリ
No | インスタンスが同じかどうか | インスタンスメソッド | syncronizedインスタンスメソッド | クラスメソッド | syncronizedクラスメソッド | 結果 |
---|---|---|---|---|---|---|
1 | 同一インスタンス | ● | 可能 | |||
2 | 同一インスタンス | ○ | ○ | 可能 | ||
3 | 同一インスタンス | ○ | ○ | 可能 | ||
4 | 同一インスタンス | ○ | ○ | 可能 | ||
5 | 同一インスタンス | ● | 不可 | |||
6 | 同一インスタンス | ○ | ○ | 可能 | ||
7 | 同一インスタンス | ○ | ○ | 可能 | ||
8 | 同一インスタンス | ● | 可能 | |||
9 | 同一インスタンス | ○ | ○ | 可能 | ||
10 | 同一インスタンス | ● | 不可 | |||
11 | 別インスタンス | ● | 略 | |||
12 | 別インスタンス | ○ | ○ | 略 | ||
13 | 別インスタンス | ○ | ○ | 略 | ||
14 | 別インスタンス | ○ | ○ | 略 | ||
15 | 別インスタンス | ● | 可能 | |||
16 | 別インスタンス | ○ | ○ | 略 | ||
17 | 別インスタンス | ○ | ○ | 略 | ||
18 | 別インスタンス | ● | 略 | |||
19 | 別インスタンス | ○ | ○ | 略 | ||
20 | 別インスタンス | ● | 不可 |
1.インスタンスメソッドとインスタンスメソッド
同時実行可能
@Test public void 同時実行可_インスタンスメソッド() throws InterruptedException { MyClass clazz = new MyClass(); Runnable task = clazz::myInstanceMethod; new Thread(task).start(); new Thread(task).start(); Thread.sleep(10000); }
結果
Thread-1: myInstanceMethod start Thread-2: myInstanceMethod start Thread-1: myInstanceMethod end Thread-2: myInstanceMethod end
2.インスタンスメソッドとsyncronizedインスタンスメソッド
同時実行可能。
@Test public void 同時実行可_インスタンスメソッドとsyncインスタンスメソッド() 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-1: myInstanceMethod start Thread-2: syncInstanceMethodA start Thread-1: myInstanceMethod end Thread-2: syncInstanceMethodA end
3.インスタンスメソッドとクラスメソッド
同時実行可能
@Test public void 同時実行可_インスタンスメソッドとクラスメソッド() throws InterruptedException { MyClass clazz = new MyClass(); Runnable taskInstance = clazz::myInstanceMethod; Runnable taskStatic = MyClass::myStaticMethod; new Thread(taskInstance).start(); new Thread(taskStatic).start(); Thread.sleep(10000); }
Thread-2: myStaticMethod start Thread-1: myInstanceMethod start Thread-1: myInstanceMethod end Thread-2: myStaticMethod end
4.インスタンスメソッドとsyncronizedクラスメソッド
同時実行可能
@Test public void 同時実行可_syncインスタンスメソッドとクラスメソッド() throws InterruptedException { MyClass clazz = new MyClass(); Runnable taskInstance = clazz::syncInstanceMethodA; Runnable taskStatic = MyClass::myStaticMethod; new Thread(taskInstance).start(); new Thread(taskStatic).start(); Thread.sleep(10000); }
結果
Thread-2: myStaticMethod start Thread-1: syncInstanceMethodA start Thread-2: myStaticMethod end Thread-1: syncInstanceMethodA end
5.syncインスタンスメソッドとsyncインスタンスメソッド
同時実行は不可です。この結果からもわかるように syncronized
がメソッド修飾子として指定されているオブジェクトのロックを取得するときは、メソッドのロックをしているわけではなく、参照先インスタンスのオブジェクトのロックを取得していることになります。
@Test public void 同時実行不可_syncインスタンスメソッドとsyncインスタンスメソッド() throws InterruptedException { MyClass clazz = new MyClass(); Runnable taskSyncA = clazz::syncInstanceMethodA; Runnable taskSyncB = clazz::syncInstanceMethodB; new Thread(taskSyncA).start(); new Thread(taskSyncB).start(); Thread.sleep(10000); }
結果
Thread-1: syncInstanceMethodA start Thread-1: syncInstanceMethodA end Thread-2: syncInstanceMethodB start Thread-2: syncInstanceMethodB end
6.syncインスタンスメソッドとクラスメソッド
同時実行可能
@Test public void 同時実行可_syncインスタンスメソッドとクラスメソッド() throws InterruptedException { MyClass clazz = new MyClass(); Runnable taskSync = clazz::syncInstanceMethodA; Runnable taskStatic = MyClass::myStaticMethod; new Thread(taskSync).start(); new Thread(taskStatic).start(); Thread.sleep(10000); }
結果
Thread-1: syncInstanceMethodA start Thread-2: myStaticMethod start Thread-1: syncInstanceMethodA end Thread-2: myStaticMethod end
7.syncインスタンスメソッドとsyncクラスメソッド
同時実行可能。インスタンスのロックとクラスのロックは異なるため、同時に実行できます。書籍にも以下のようにあります。
staticの同期されたメソッドでClassオブジェクトのロックを獲得することで、そのクラスのオブジェクトに何らかの影響を与えたりはしません。他のスレッドがstaticの同期されたメソッドでClassオブジェクトのロックを保持していても、そのクラスのオブジェクトに対する同期されたメソッドの呼び出しができます。
@Test public void 同時実行可_syncインスタンスメソッドとstaticメソッド() throws InterruptedException { MyClass clazz = new MyClass(); Runnable taskSync = clazz::syncInstanceMethodA; Runnable taskSyncStatic = MyClass::syncStaticMethodA; new Thread(taskSync).start(); new Thread(taskSyncStatic).start(); Thread.sleep(10000); }
結果
Thread-2: syncStaticMethodA start Thread-1: syncInstanceMethodA start Thread-2: syncStaticMethodA end Thread-1: syncInstanceMethodA end
8.クラスメソッドとクラスメソッド
同時実行可能
@Test public void 同時実行可_クラスメソッドとクラスメソッド() throws InterruptedException { Runnable taskStaticA = MyClass::myStaticMethod; Runnable taskStaticB = MyClass::myStaticMethod; new Thread(taskStaticA).start(); new Thread(taskStaticB).start(); Thread.sleep(10000); }
結果
Thread-1: myStaticMethod start Thread-2: myStaticMethod start Thread-1: myStaticMethod end Thread-2: myStaticMethod end
9.クラスメソッドとsyncクラスメソッド
@Test public void 同時実行可_syncインスタンスメソッドとstaticメソッド() throws InterruptedException { Runnable taskStatic = MyClass::myStaticMethod; Runnable taskSyncStatic = MyClass::syncStaticMethodA; new Thread(taskStatic).start(); new Thread(taskSyncStatic).start(); Thread.sleep(10000); }
結果
Thread-2: syncStaticMethodA start Thread-1: myStaticMethod start Thread-1: myStaticMethod end Thread-2: syncStaticMethodA end
10.syncクラスメソッドとsyncクラスメソッド
同時実行不可。
@Test public void 同時実行不可_syncクラスメソッドとsyncクラスメソッド() throws InterruptedException { Runnable taskSyncStatic = MyClass::syncStaticMethodA; new Thread(taskSyncStatic).start(); new Thread(taskSyncStatic).start(); Thread.sleep(10000); }
結果
Thread-1: syncStaticMethodA start Thread-1: syncStaticMethodA end Thread-2: syncStaticMethodA start Thread-2: syncStaticMethodA end
15.異なるインスンタンスでsyncインスタンスメソッドとsyncインスタンスメソッド
同時実行可能。synchronizedはそのインスタンスのオブジェクトに対するロックを取得するため、インスタンスが異なればロックも異なるため同時に実行できます。
@Test public void 同時実行可_異なるインスタンスの異なるsyncメソッド() 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(10000); }
結果
Thread-1: syncInstanceMethodA start Thread-2: syncInstanceMethodB start Thread-1: syncInstanceMethodA end Thread-2: syncInstanceMethodB end
20.異なるsyncクラスメソッド
同時実行不可。異なるsynchronizedクラスメソッドでも、同じクラスのロックを取得するため、同時実行できません。
@Test public void 同時実行不可_異なるsyncクラスメソッド() throws InterruptedException { Runnable taskStaticSyncA = MyClass::syncStaticMethodA; Runnable taskStaticSyncB = MyClass::syncStaticMethodB; new Thread(taskStaticSyncA).start(); new Thread(taskStaticSyncB).start(); Thread.sleep(10000); }
結果
Thread-1: syncStaticMethodA start Thread-1: syncStaticMethodA end Thread-2: syncStaticMethodB start Thread-2: syncStaticMethodB end
syncronized文
synchronizedメソッドよりも短い領域/期間だけ同期することができます。これはパフォーマンスの観点から長い領域に対するロックよりも有効です。
synchronized文は以下のような形になります。exprにはオブジェクト参照の結果が入ります。ロックを取得するとブロック内のstatementが実行されます。
synchronized (expr) {
statement
}
ロック対象という意味では以下のコードは同じです。コンパイル後のバイトコードは異なります。
synchronizedメソッド
public synchronized void syncInstanceMethodA() { System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start"); sleep(); System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end"); }
synchronized文
public void syncInstanceMethodA() { synchronized (this) { System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start"); sleep(); System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end"); } }
またコンストラクタにsynchronized文を組み込むことができます。thisに対して同期をとると、各呼び出しで異なるオブジェクトが使用されるため、排他制御が担保できません。
public MyClassStatement(int valA, int valB) { synchronized (MyClassStatement.class) { this.valA = valA; this.valB = valB; } }
確認してみる
実験用メソッドのコード
package sample_14_3;
public class MyClassStatement {
int valA = 0, valB = 0;
Object lockA = new Object();
Object lockB = new Object();
public int syncGetA() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " locked.");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
return valA;
}
}
public void syncIncA() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " locked.");
sleep();
valA++;
}
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
public int syncGetB() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " locked.");
sleep();
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
return valB;
}
}
public void syncIncB() {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " start");
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " locked.");
sleep();
valB++;
}
System.out.println(Thread.currentThread().getName() + ": " + Thread.currentThread().getStackTrace()[1].getMethodName() + " end");
}
private static void sleep() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ObjectAに対するロック
同時実行不可です。ObjectAのオブジェクトのロック待ちになるためです。
@Test public void 同時実行不可_objectAのロック取得待ち() throws InterruptedException { MyClassStatement clazz1 = new MyClassStatement(); Runnable taskSyncStaticA1 = clazz1::syncGetA; Runnable taskSyncStaticA2 = clazz1::syncIncA; new Thread(taskSyncStaticA1).start(); new Thread(taskSyncStaticA2).start(); Thread.sleep(10000); }
結果
Thread-2: syncIncA start Thread-1: syncGetA start Thread-2: syncIncA locked. Thread-1: syncGetA locked. Thread-2: syncIncA end Thread-1: syncGetA end
ObjectAとObjectBに対するロック
同時実行可能です。ロックするオブジェクトが異なるためです。
public void 同時実行可_objectAとObjectBのロック() throws InterruptedException { MyClassStatement clazz1 = new MyClassStatement(); Runnable taskSyncStaticA1 = clazz1::syncGetA; Runnable taskSyncStaticB1 = clazz1::syncIncB; new Thread(taskSyncStaticA1).start(); new Thread(taskSyncStaticB1).start(); Thread.sleep(10000); }
結果
Thread-2: syncIncB start Thread-1: syncGetA start Thread-2: syncIncB locked. Thread-1: syncGetA locked. Thread-2: syncIncB end Thread-1: syncGetA end