basyura's blog

あしたになったらほんきだす。

ThreadLocal な変数はスレッド番号が同じだと共有されちゃう

てなことを先日初めて知った。

各スレッドは 、スレッドが生存していて ThreadLocal インスタンスがアクセス可能である間は、スレッドローカル変数のコピーへの暗黙的な参照を保持します。スレッドが終了すると、スレッドローカルインスタンスのコピーは、すべてガベージコレクトされます (これらのコピーへの参照がほかに存在する場合を除く)。

ThreadLocal

読み取れるような、読み取れないような・・・。問題が発生するのはこんなとき。 A さんが "Thread-1" の ThreadLocal な変数に A をセットしてレスポンス終了。その後、"Thread-1" が破棄されずに B さんに使われて ThreadLocal な変数を参照すると A さんがセットした A を参照できる。
Sastruts のチュートリアルを使ってみた(mac 環境で eclipsetomcattomcat-plugin を入れてってするのが久々すぎて辛かった・・・)。

@Execute(validator = false)
public String index() {
	try {
		FileOutputStream fos = new FileOutputStream("/tmp/log.txt" , true);
		PrintWriter pw = new PrintWriter(fos , true);
		long   id   = Thread.currentThread().getId();
		String name = Thread.currentThread().getName();
		HashMap map = (HashMap)serialNum.get();
		ArrayList list = (ArrayList)map.get("test");
		if(list == null) {
			list = new ArrayList();
			map.put("test" , list);
		}
		list.add("aaa");
		pw.println(name + " " + id + " " + list.size());
		pw.close();
		fos.close();
	} catch (Exception e) {
		e.printStackTrace();
	}
	return "index.jsp";
}
private static ThreadLocal serialNum = new ThreadLocal() {
	protected synchronized Object initialValue() {
		return new HashMap();
	}
};

ゴリゴリなのは置いといて・・・。リクエストごとに ThreadLocal な変数(ArrayList)を参照して文字列を追加。スレッド番号とサイズを出力する。

http-8080-Processor23 37 1
http-8080-Processor23 37 2
http-8080-Processor23 37 3
http-8080-Processor23 37 4
http-8080-Processor24 38 1
http-8080-Processor24 38 2
http-8080-Processor24 38 3
http-8080-Processor22 36 1
http-8080-Processor22 36 2
http-8080-Processor22 36 3
http-8080-Processor24 38 4
http-8080-Processor22 36 4

こんな感じで、同じスレッド番号の場合はサイズが増えていくし、スレッド番号が切り替わると 1 から始まる。使う側がフィルタなりで初期化と後始末をきっちりしないといけないのね。
スレッドがプールされて使いまわされるからと言われれば「そりゃ共有されるわ」と思う。けど、実装しててスレッドがプールされてるからウンチャラカンチャラなんて考えないもんなぁ。常識なんだろうか。マルチスレッドだからリクエストのタイミングとか同期のタイミングがウンチャラカンチャラは考えるんだけど。