RE: 一定時間しか保持しないMap
id:Yoshioriが何か変な事してたんで、考えてみた。
ぱっと思いついた問題点。
- cleanメソッドがヒドイ。
- あらゆるMapオペレーションの度に、値を全走査するとか、無いんじゃね?
- TimeAndValueがヒドイ。
- Dateじゃなくて、long持てば良くね?
- 非同期処理中に使うとハマるんじゃね?
- 何にこの実装を使うのか知らんけど、キャッシュとかじゃねぇの?
- TimeoutMap
import java.io.Serializable; import java.util.AbstractMap; import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class TimeoutMap<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V>, Serializable { private static final long serialVersionUID = 9162835424791321760L; protected ScheduledExecutorService cleaner = Executors .newScheduledThreadPool(1); protected ConcurrentMap<K, V> delegate; protected final long timeout; public TimeoutMap(long timeout) { this.delegate = new ConcurrentHashMap<K, V>(); this.timeout = timeout; } public TimeoutMap(long timeout, int initialCapacity) { this.delegate = new ConcurrentHashMap<K, V>(initialCapacity); this.timeout = timeout; } public void dispose() { cleaner.shutdown(); } public void clear() { delegate.clear(); } public boolean containsKey(Object key) { return delegate.containsKey(key); } public boolean containsValue(Object value) { return delegate.containsValue(value); } public Set<java.util.Map.Entry<K, V>> entrySet() { return delegate.entrySet(); } public boolean equals(Object o) { return delegate.equals(o); } public V get(Object key) { return this.delegate.get(key); } public int hashCode() { return this.delegate.hashCode(); } public boolean isEmpty() { return this.delegate.isEmpty(); } public Set<K> keySet() { return this.delegate.keySet(); } public V put(final K key, V value) { V result = delegate.put(key, value); scheduleRemove(key); return result; } protected void scheduleRemove(final K key) { cleaner.schedule(new Runnable() { @Override public void run() { delegate.remove(key); } }, timeout, TimeUnit.MILLISECONDS); } public void putAll(Map<? extends K, ? extends V> m) { for (K key : m.keySet()) { scheduleRemove(key); } delegate.putAll(m); } public V putIfAbsent(final K key, V value) { V result = delegate.putIfAbsent(key, value); scheduleRemove(key); return result; } public boolean remove(Object key, Object value) { return delegate.remove(key, value); } public V remove(Object key) { return delegate.remove(key); } public boolean replace(K key, V oldValue, V newValue) { scheduleRemove(key); return delegate.replace(key, oldValue, newValue); } public V replace(K key, V value) { scheduleRemove(key); return delegate.replace(key, value); } public int size() { return delegate.size(); } public Collection<V> values() { return delegate.values(); } }
- 超テキトーなテストコード
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import org.junit.Test; public class TimeoutMapTest { @Test public void testPutKV() throws Exception { TimeoutMap<String, String> map = new TimeoutMap<String, String>(1000); try { map.put("aaa", "1111"); map.put("bbb", "2222"); map.put("ccc", "3333"); map.put("ddd", "4444"); for (String v : map.values()) { System.out.println(v); } Thread.sleep(1000); assertEquals(0, map.size()); assertNull(map.get("aaa")); } finally { map.dispose(); } } }
ぱっと思いついたまんま実装したので、id:Yoshioriの実装で抱えている問題点は全部解決している。
但し、全然違う問題を抱え込む事になっている。
- メモリの使い方が超贅沢
- cleaner用に新しいThreadが立ちます。
- ConcurrentHashMapは、超贅沢なMapです。
マルチスレッド環境において、そのコストに正当性は勿論あるのだけど、もし、シングルスレッド環境で使うなら、そこに妥当性は存在しませぬ。
- timeout時の自動削除は、規定の時間を越えても動作しない可能性があります。
- cleanerスレッドは1つしか立ててないし、そのスレッド数を外からカスタマイズ出来ないので、
物凄い量のエントリを突っ込むと多分きっと間違いなくtimeoutしてる筈なのに、消えないエントリが出てくるます。
- cleanerスレッドは1つしか立ててないし、そのスレッド数を外からカスタマイズ出来ないので、
- TimeoutMapを使い終わったら、disposeメソッドを呼ばないと、cleanerスレッドが残ったままになるます。つまりメモリリークの危険がある。
こんな感じでどうか?
追記::
おかしな部分があったので修正。