LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 1 year has passed since last update.

スレッドセーフに再帰処理

Last updated at Posted at 2022-06-06

スレッドセーフにしたい

スタック領域とヒープ領域が存在する
ざっくり、各スレッドごとにヒープ領域をもつ。
とても分かりやすい記事があるので参照されたし

ThreadLocalを使う

スレッドごとに保護された空間をつかっていく。
これをしないと
スレッド1の挙動 ①1週目→②3週目
スレッド2の挙動 ①2週目→②4週目
とかってなる。理想は
スレッド1の挙動 ①1週目→②2週目
スレッド2の挙動 ①1週目→②2週目

test.java
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になったりする。

で再帰処理

カウンタをスレッドごとに分離したので、あとはただ再帰処理を行うだけ。

result.java
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でよいが
配列でやりたいならこういうこともできる

arr.java
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を付けたらよいだけ。

synchronized.java
private synchronized void test() {
  System.out.println("このメソッドは同時起動しません");
}
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up