この記事は、シアトルコンサルティング株式会社 Advent Calendar 2021の2日目の記事です。
こんにちは、シアトルコンサルティングの長岡です。
はじめに
前回はプログラムをマルチスレッドで実行してみました。
Javaのマルチスレッド実装例(前編)
今回はマルチスレッドでプログラムを実行した際の注意点として上げられる、排他制御について書いていこうと思います。
今回も前回同様駆け出しエンジニアの方達にも理解できる内容となっております!
なぜ排他制御が必要なのか?
前回の記事でも書きましたが、マルチスレッドの処理で同じオブジェクトにアクセスしてしまうと同じフィールドの値を書き換えてしまう為、最終的に期待した値が帰ってこないことがあります。
その為、同じオブジェクトにアクセスする場合はsynchronizedを使用し、排他制御を実装する必要があります。
排他制御の実装
排他制御を実装するにあたり、まずは排他制御なしでプログラムを動かしてみます。
プログラムの内容は初期値0の共通オブジェクトに対し、1000追加するメソッドを1000スレッドで動かし、最後にオブジェクトの値を出力します。
想定通りの動きをするパターンもある為、上記のプログラムをfor文で10回動かしてみます。
package main;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 10; i++) {
Bank.bank = 0;
int threadNum = 1000;
// 引数に生成するスレッド数を渡す
ExecutorService exec = Executors.newFixedThreadPool(threadNum);
Callable<String> sub1 = new Sub1();
List<Callable<String>> tasklist = new ArrayList<>();
for(int j = 0; j < threadNum; j++) {
tasklist.add(sub1);
}
List<Future<String>> future = exec.invokeAll(tasklist);
System.out.println(Bank.bank);
exec.shutdown();
}
}
}
package main;
import java.util.concurrent.Callable;
public class Sub1 implements Callable<String>{
public void addBank() {
Bank.bank += 1000;
}
@Override
public String call() throws Exception {
addBank();
return null;
}
}
package main;
public class Bank {
public static int bank = 0;
}
999000
1000000
1000000
1000000
1000000
1000000
1000000
1000000
1000000
1000000
一番最初の出力が999000になっていますね。
bankの値が998000のタイミングで同時に1000追加する処理を行った為、このような結果になったのだと思います。
今回の様な結果を防ぐ為、排他制御を実装していきます。
排他制御の実装は非常に簡単です。
下記の様にスレッドで呼び出すメソッドにsynchronizedをつけるだけです。
public synchronized void addBank() {
Bank.bank += 1000;
}
では、実行してみます。
1000000
1000000
1000000
1000000
1000000
1000000
1000000
1000000
1000000
1000000
全て想定通り出力されていますね。