解説
マルチスレッドのプログラムでは、思わぬ問題に遭遇することがあります。それは例えば、並行して実行している複数のスレッドが、共通のデータを読み込んだり、書き換えたりした際に起きます。
スレッドAがデータを読み込み、スレッドBがデータを読み込み、スレッドAが変更した値を保存し、スレッドBが変更した値を保存したとします。この場合、スレッドAが変更した内容が、Bによって上書きされてしまいます。
スレッドA データ スレッドB ↓ □□□ ↓ データ読込←←←□□□ ↓ □□□ □□□→→→データ読込 ↓ □□□ □□□ データ書換 □□□ ↓ □■□ □□□ ↓ ↓ □□□ データ書換 ↓ □□□ ■□■ ↓ □□□ ↓ データ保存→→→□■□ ↓ □■□ ■□■←←←データ保存 ↓ ■□■ ■□■ ↓ ■□■ ↓
こういった問題を避けるためには、スレッドがデータに対して処理をしている間、他のスレッドがそのデータにアクセスすることに制限を設ける必要があります。そういった制限を設ける方法のことを「排他制御」と呼びます。
例えば、上記の処理に、制限を与えると、以下のようになります。
スレッドA データ スレッドB ↓ □□□ ↓ 排他制御開始─────┐ ↓ データ読込←←←□□□│ ↓ □□□ □□□│ 排他制御中なので待機 ↓ □□□│ 排他制御中なので待機 データ書換 □□□│ 排他制御中なので待機 □■□ □□□│ 排他制御中なので待機 ↓ □□□│ 排他制御中なので待機 データ保存→→→□■□│ 排他制御中なので待機 □■□ □■□│ 排他制御中なので待機 排他制御終了─────┘ 排他制御中なので待機 ↓ ┌──────排他制御開始 ↓ │□■□→→→データ読込 ↓ │□■□ □■□ ↓ │□■□ ↓ ↓ │□■□ データ書換 ↓ │□■□ ■■■ ↓ │□■□ ↓ ↓ │■■■←←←データ保存 ↓ │■■■ ■■■ ↓ └──────排他制御終了 ↓ ■■■ ↓ ↓ ■■■ ↓
サンプル
排他制御の仕組みが組み込まれているJavaで、排他制御されたプログラムを書いてみます。
package sample;
import java.util.Arrays;
public class MainClass {
static final int SZ = 16;
public static void main(String[] args) {
// 同期なし
System.out.println("同期なし");
execThread1();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 同期あり
System.out.println("\n\n同期あり");
execThread2();
}
static void out(String tag, int n0, int n1) {
System.out.println(tag + " : " + n0 + " ? " + n1 + " = " + (n0 == n1));
}
static void sleep() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 同期なし
static void execThread1() {
final int[] ARR = new int[SZ];
Arrays.fill(ARR, 100);
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < SZ; i ++) {
int n = ARR[i] + 10;
sleep();
ARR[i] = n;
out("@thread1@", n, ARR[i]);
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < SZ; i ++) {
int n = ARR[i] + 1;
sleep();
ARR[i] = n;
out("#thread2#", n, ARR[i]);
}
}
});
thread1.start();
thread2.start();
}
// 同期あり
static void execThread2() {
final int[] ARR = new int[SZ];
Arrays.fill(ARR, 100);
final Object sync = new Object();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < SZ; i ++) {
synchronized (sync) {
int n = ARR[i] + 10;
sleep();
ARR[i] = n;
out("@thread1@", n, ARR[i]);
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < SZ; i ++) {
synchronized (sync) {
int n = ARR[i] + 1;
sleep();
ARR[i] = n;
out("#thread2#", n, ARR[i]);
}
}
}
});
thread1.start();
thread2.start();
}
}
同期なし #thread2# : 101 ? 101 = true @thread1@ : 110 ? 101 = false #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 101 = false @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 101 = false #thread2# : 101 ? 101 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true #thread2# : 101 ? 101 = true @thread1@ : 110 ? 101 = false @thread1@ : 110 ? 110 = true #thread2# : 101 ? 101 = true 同期あり @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true @thread1@ : 110 ? 110 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true #thread2# : 111 ? 111 = true
