java.lang.ThreadLocal クラス
Java 2 SE 5.0 から、ThreadLocalクラスにremove()メソッドが追加された。
Java 2 SE 1.4 の場合、ThreadLocalクラスの使用例はこんな感じ。
Removes the value for this ThreadLocal. This may help reduce the storage requirements of ThreadLocals. If this ThreadLocal is accessed again, it will by default have its initialValue.
ThreadLocal var = new ThreadLocal(); System.out.println("1: " + var.get()); var.set("hello"); System.out.println("2: " + var.get()); var.set(null); System.out.println("3: " + var.get());
一方、Java 2 SE 5.0 の場合は、こんな感じ。
ThreadLocal<String> var = new ThreadLocal<String>(); System.out.println("1: " + var.get()); var.set("hello"); System.out.println("2: " + var.get()); var.remove(); System.out.println("3: " + var.get());
で、いずれも次のような出力を得る。
1: null 2: hello 3: null
しかし、「set(null)」と「remove()」は必ずしも等価ではない。
remove()メソッドのJavaDocにも書いてあるように、remove()呼び出し後に再度アクセスした場合はinitialValueを持つ、とある。
例えばこんな感じである。
ThreadLocal<String> var = new ThreadLocal<String>() { protected String initialValue() { return "world"; } }; System.out.println("1: " + var.get()); var.set("hello"); System.out.println("2: " + var.get()); var.set(null); System.out.println("3: " + var.get()); var.remove(); System.out.println("4: " + var.get());
この場合、結果は次のようになる。
1: world 2: hello 3: null 4: world
また、JavaDocにある、必要な記憶領域の削減、というのは、ThreadLocalクラスのソースを見ると分かるが、「set(null)」ではnullを値とするエントリが保持されるが、「remove()」を呼ぶとそのエントリ自体が削除される。
これを、ThreadLocalクラスのソースを参考に作ったプログラムで確認してみる。
ThreadLocal<String> var = new ThreadLocal<String>() { protected String initialValue() { return "world"; } }; Thread t = Thread.currentThread(); // java.lang.reflect.Field Field fMap = t.getClass().getDeclaredField("threadLocals"); fMap.setAccessible(true); Object map = fMap.get(t); // 実体は ThreadLocal.ThreadLocalMap オブジェクト Field fSize = map.getClass().getDeclaredField("size"); fSize.setAccessible(true); System.out.println("1: " + fSize.getInt(map)); var.set("hello"); System.out.println("2: " + fSize.getInt(map)); var.set(null); System.out.println("3: " + fSize.getInt(map)); var.remove(); System.out.println("4: " + fSize.getInt(map)); var.get(); System.out.println("5: " + fSize.getInt(map)); fSize.setAccessible(false); fMap.setAccessible(false);
これを実行すると、
1: 4 2: 5 3: 5 4: 4 5: 5
で、確かに一旦は保持されたエントリが、remove()メソッドの呼び出しによって削除されていることが確認できる。さらに、get()メソッドを呼び出すと、初期値が保持される分だけエントリ数が増えている。
ここまで書いておいてなんだが、実は、そもそも「set(null)」を呼び出すという記述自体がおかしくて、本来ならば「set(初期値)」であるべきなのである。initialValue()メソッドをオーバーライドしない場合はこの「初期値」がnullであるから区別がつかないだけで。