スレッドセーフにしたい
スタック領域とヒープ領域が存在する
ざっくり、各スレッドごとにヒープ領域をもつ。
とても分かりやすい記事があるので参照されたし
ThreadLocalを使う
スレッドごとに保護された空間をつかっていく。
これをしないと
スレッド1の挙動 ①1週目→②3週目
スレッド2の挙動 ①2週目→②4週目
とかってなる。理想は
スレッド1の挙動 ①1週目→②2週目
スレッド2の挙動 ①1週目→②2週目
private ThreadLocal<Integer> threadSafeCnt = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
// 反復試行回数カウンタの初期値
return 1;
}
};
// スタック領域に反復試行回数カウンタの値をコピーして保持
int cnt = threadSafeCnt.get();
// スタック領域のカウンタをインクリメントしretry
threadSafeCnt.set(cnt++);
このように、カウンタを各スレッドごとに別にすればOK
たとえば5回リトライしたら失敗とみなす
みたいな処理をログイン画面にいれたとき
2スレッドで、スレッドセーフにせずに実装すると、1回しか失敗してないのに別画面での失敗分もカウントされてソッコーNGになったりする。
で再帰処理
カウンタをスレッドごとに分離したので、あとはただ再帰処理を行うだけ。
private ThreadLocal<Integer> threadSafeCnt = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
// 反復試行回数カウンタの初期値
return 1;
}
};
private void test() {
// スタック領域に反復試行回数カウンタの値をコピーして保持
int cnt = threadSafeCnt.get();
// do something
System.out.println("いま" + cnt + "週目");
// 終了条件
if (cnt == 5) {
threadSafeCnt.set(1);
System.out.println("終了")
return;
}
// スタック領域のカウンタをインクリメントしretry
threadSafeCnt.set(cnt++);
// 再帰
test();
}
// ini
test();
これで実現できるはず。
スレッドセーフな配列
変数をスレッドセーフにするならThreadLocalでよいが
配列でやりたいならこういうこともできる
List<String> list = Collections.synchronizedList(new ArrayList<String>());
List<String> これもOK = Collections.synchronizedList(new LinkedList<String>());
Collections.synchronizedList
で宣言することでスレッドセーフになるわけだ
ちなみにLinkedList と ArrayList の使い分け方はこちら
余談
また、どのスレッドから呼ばれても、同時スレッドを1つに制限することで、同時でなく順番に実行する方法もある
※1個ずつ処理したところでカウンタはスレッドごとに増やしちゃうから、ここでは無意味
スレッド1の挙動 ①メソッド起動
スレッド2の挙動 ①メソッド終わるまで待つ→②メソッド起動
といった具合
synchronizedを付けたらよいだけ。
private synchronized void test() {
System.out.println("このメソッドは同時起動しません");
}