競合とは
一言で言うと、複数のスレッドで一つのインスタンスを共有すると競合が起きます。こうなることで、インスタンスのフィールドの値を読みだしてから、変更するまでの間に 、別のスレッドが変更してしまいます。いつ実行されるかはプログラミング側で制御することが出来ません。飲食店に例えると、あるお客さんがカレーを注文したが、料理人がカレーを作っている間にカレーからオムライスに変更されてしまったというイメージです。
対策
結論から言うと、対策はスレッドから処理している間は別のスレッドで処理が実行されないようにすることです。これを__排他制御__と呼びます。排他制御の方法を紹介していきます。
方法
__synchronizedキーワード__を使用します。パターンは2つあります。
①メソッド宣言に使う場合
②メソッド内の1部の処理だけを対象に使う場合
メソッド宣言に使う場合
__synchronized__を修飾するだけです。
しかし、メソッド内の処理が複雑であればあるほど、待ち時間が出来てしまいます。
public synchronized void method(){
doSomething;
}
メソッド内の一部の処理だけを対象に使う場合
syncronizedブロックを使用するだけです。どのインスタンスに対して、排他制御するのかをブロック引数として指定します。一部の制御をすることで、待ち時間を最小限にすることが出来ます。
void method(){
syncronized(instance){
doSomething()
}
問題点
複数のスレッド間で排他制御された複数のインスタンスを共有していて、それぞれのインスタンス同士が連携することで、__デットロック__が発生してしましいます。解決方法として、ロックする順番をスレッド間で揃える方法があります。また、syncnizedは別のスレッドを待機させるので、パフォーマンスに影響を及ぼしてしまいます。その対策として、単純な処理には実行されないか、一連の処理が完全に終わるかになるような結果が保証されているjava.util.concurrent.atomicパッケージを利用します。
java.util.concurrent.atomicパッケージのAtomicIntegerクラスの使用例
atomicパッケージのAtomicIntegerクラスを使用して、排他制御していきます。
atomicパッケージにどのようなクラスがあるのかは、下記のサイトを参考にしてください。
public class Value {
private int num = 0;
public void add(int num) {
this.num +=num;
}
public int get() {
return num;
}
}
AtomicIntegerクラスのaddAndGedメソッドを使うことで、値の呼び出しから、変更までの間に他のスレッドは処理できない。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicValue extends Value{
private AtomicInteger num = new AtomicInteger(0);
public void add(int num) {
//読み出しから値の変更まで他のスレッドからの処理は受けない
this.num.addAndGet(num);
}
public int get() {
return this.num.intValue();
}
}
public class Sample {
public static void main(String[] args) {
Value val = new AtomicValue();
//スレッドプールを2つ作成
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.submit(new Task(val));
exec.submit(new Task(val));
try {
Thread.sleep(200);
}catch(InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(val.get());
exec.shutdown();
}
}
200
まとめ
競合を防ぐにはsyncronizedキーワードを使用する
デットロックを防ぐにはロックする順番をスレッド間で揃える