弱参照
JAVAでは、他から参照されなくなったインスタンスはガベージコレクション(GC)が実行されると消滅する。
逆に言うと、参照されているインスタンスはずっと消えない。
でも弱参照されているインスタンスはGCで消える。
普段使っている参照は強参照。
String s = "文字列";
例えば、上記のようにsを宣言したメソッドが続いている限り、"文字列"はメモリに残り続ける。
下は弱参照
WeakReference<String> wrs = new WeakReference<String>("文字列");
"文字列"はwrsのgetメソッドで取得できる。
wrs自体は強参照なので、メソッドが続いている限りメモリに残り続ける。
しかし、wrsの中に入れた"文字列"は弱参照になる。
例えば、wrsの宣言直後にGCを実行すると、"文字列"はメモリから消えて取得できなくなる。
弱参照マップ
WeakHashMapは、キーにしたインスタンスを弱参照する。
キーにしたインスタンスの参照がWeakHashMap以外からなくなってGCが行われれば、WeakHashMapからキーのエントリごと消える。
弱参照の使いどころ
いまいち使いどころのない弱参照ではあるが、
消えるインスタンスと消したくないインスタンスの組み合わせで活用した。
スレッド毎に一意なインスタンスを再利用する
スレッド毎に一意なインスタンスを保証する場合、ThreadLocalを使えばいい。
ThreadLocalなら、インスタンスがそのスレッドからしか参照されていない場合は、スレッドが消えるとインスタンスも消える。
では、スレッドに参照されなくなったインスタンスを他のスレッドに再利用させたい場合に弱参照マップが活用できた。
スレッド毎に一意なインスタンスを確保しつつ、そのスレッドが消えると確保されていたインスタンスは新たにインスタンスを確保したいスレッドに再利用されるようにできる。
import java.util.ArrayList;
import java.util.List;
import java.util.WeakHashMap;
/**
* Keep element of each thread, and another thread can reuse element when that thread disappears.
*
* @param <E> The element type of this pool
*/
public class RecyclePool<E> {
private final List<E> elementList = new ArrayList<>();
private final WeakHashMap<Thread, E> weakElementMap = new WeakHashMap<>();
/**
* Appends the specified element to the this pool.
*
* @param element element to be appended to this pool
*/
public void add(E element) {
synchronized (elementList) {
elementList.add(element);
weakElementMap.put(Thread.currentThread(), element);
}
}
/**
* Returns the number of elements in this pool.
*
* @return the number of elements in this pool
*/
public int size() {
return elementList.size();
}
/**
* Returns the element keeping by current thread. if current thread does not keep element then,
* keeping and returning the element kept by disappear thread. or {@code null} if all elements of
* this pool are kept by other threads.
*
* @return the element kept by current thread
*/
public E get() {
Thread currentThread = Thread.currentThread();
E myElement = weakElementMap.get(currentThread);
if (myElement != null) {
return myElement;
}
synchronized (elementList) {
if (elementList.size() <= weakElementMap.size()) {
return null;
}
for (E element : elementList) {
if (!weakElementMap.containsValue(element)) {
weakElementMap.put(currentThread, element);
myElement = element;
break;
}
}
}
return myElement;
}
/**
* Execute garbage collection for reusing elements kept by the thread to be disappear.
*/
public void refresh() {
for (int retry = 0; retry <= 10; retry++) {
System.gc();
if (elementList.size() > weakElementMap.size()) {
return;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
;
}
}
}
}
addメソッドでスレッド毎に確保したいインスタンスをセットして、getメソッドで取得する。
addしたスレッドとは別のスレッドがgetしても、インスタンスは取得できない。
しかし、addしたスレッドが既にGCで消えているなら、別のスレッドでも取得できて、さらに別のスレッドからは取得できなくなる。
スレッドがアクティブでなくなった&参照がなくなっても、GCが行われないとインスタンスが確保されたままなので、
refreshメソッドで手動GCさせて無理やり解放させるようにした。
このソースはnumatrixで実際に使っている。
numatrixではスレッド毎に一意なNumatrixNumberGeneratorの上限数があるため、スレッドを次々と作られるような使い方をされると、NumatrixNumberGeneratorの数が上限数に達してしまうという懸念があった。
RecyclePoolを使ってNumatrixNumberGeneratorをスレッド毎に確保しつつ、使われなくなったNumatrixNumberGeneratorを別のスレッドに再利用している。