目標是想要有個 Map,能夠快速儲存、取得我存在 Map 上的某個 Object;但當 Object 不再使用的時候,它又能自動消失。
由於根據自己的測試(以及 [1] 中網友 Cowan 的回應),WeakHashMap 似乎不太適合這個用途,因此就想來嘗試 [1] 中提到的 MapMaker 了。
MapMaker 使用方法還蠻容易的!在初始化時可以任意指定要產生的 Map 想要 Soft Key、Weak Key、Soft Value 還是 Weak Value
同時也可以指定 Concurrency Level(即 ConcurrentHashMap 對於 Concurrency 的處理方法,其中 ConcurrentHashMap 是 16)。
範例程式碼如下:
import java.util.UUID; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantReadWriteLock; import com.google.common.collect.MapMaker; public class WeakMapTest { public static ConcurrentMap<UUID, ReentrantReadWriteLock> map = null; public static Thread a = null, b = null; public static UUID testUUID = null; public static void main(String[] args) { map = new MapMaker().weakValues().makeMap(); a = new Thread(new ThreadA()); b = new Thread(new ThreadB()); a.start(); b.start(); } private static class ThreadA implements Runnable { @Override public void run() { System.out.println("A: Start."); try { Thread.currentThread().sleep(10000); } catch (Exception e) { System.out.println("A: Start checking key."); } for (int i = 0; i < 1000000; i++) { if (map.get(testUUID) != null) { System.out.println("A: At iteration " + i + " the map still holds the reference on key. Size: " + map.size()); } else { System.out.println("A: Key has finally been garbage collected at iteration " + i + ", hence the map is now empty. Size: " + map.size()); break; } } System.out.println("A: End."); } } private static class ThreadB implements Runnable { @Override public void run() { System.out.println("B: Start."); UUID id = UUID.randomUUID(); testUUID = id; map.put(id, new ReentrantReadWriteLock()); System.out.println("B: Put into map with key " + id + "\t" + map.get(id)); System.out.println("B: Map size is " + map.size()); Thread tc = new Thread(new ThreadC()); tc.start(); try { Thread.currentThread().sleep(1000); } catch (Exception e) {} a.interrupt(); System.out.println("B: End."); } } private static class ThreadC implements Runnable { @Override public void run() { System.err.println("C: start."); System.err.println("C: Get key " + testUUID.toString()); ReentrantReadWriteLock lock = map.get(testUUID); System.err.println("C: Get lock " + lock); try { Thread.currentThread().sleep(10000); } catch (Exception e) {} System.err.println("C end."); } } }
在 13 行建立 Map 時是使用 Weak Value,也就是當 Value 沒有被任何 Thread 使用時,我希望它自動消失。
接著產生三個 Thread。Thread A 一開始就在沈睡,等待 Thread B 把它叫醒。
Thread B 在 Map 裡插入一筆資料,然後呼叫 Thread C 和 Thread A~
Thread C 從 Map 裡利用原本的 UUID 把對應的 Value Object 取出來,然後就暫停執行。
而 Thread A 在被喚醒後就開始跑迴圈一直檢查 Map,看看指定的 UUID 對應的 Value Object 是不是還在 Map 裡。
執行結果如下:
A: Start. B: Start. B: Put into map with key e262d954-cf1a-4a36-adec-182ad2381ceb java.util.concurrent.locks.ReentrantReadWriteLock@2705d88a[Write locks = 0, Read locks = 0] B: Map size is 1 C: start. C: Get key e262d954-cf1a-4a36-adec-182ad2381ceb C: Get lock java.util.concurrent.locks.ReentrantReadWriteLock@2705d88a[Write locks = 0, Read locks = 0] B: End. A: Start checking key. A: At iteration 0 the map still holds the reference on key. Size: 1 A: At iteration 1 the map still holds the reference on key. Size: 1 ... A: At iteration 137105 the map still holds the reference on key. Size: 1 C: end A: At iteration 137106 the map still holds the reference on key. Size: 1 ... A: At iteration 139112 the map still holds the reference on key. Size: 1 A: At iteration 139113 the map still holds the reference on key. Size: 1 A: Key has finally been garbage collected at iteration 139114, hence the map is now empty. Size: 0 A: End.
可以看出,從 Thread C 開始之後,一直到完全結束之前,UUID 跟對應的 Value Object 都一直存在於 Map 中。
另外當 Value 因為 Weak Reference 特性被回收時,在 Map 中對應的 Key 也會自動被刪掉,Map size 會變小。
而如果把 56 行註解掉,也就是不要執行 Thread C 去取得 Value Object 的話,結果如下:
A: Start. B: Start. B: Put into map with key c46493fe-9cb8-4eb7-86d0-7d5821e4e994 java.util.concurrent.locks.ReentrantReadWriteLock@2993a66f[Write locks = 0, Read locks = 0] B: Map size is 1 B: End. A: Start checking key. A: At iteration 0 the map still holds the reference on key. Size: 1 A: At iteration 1 the map still holds the reference on key. Size: 1 ... A: At iteration 25428 the map still holds the reference on key. Size: 1 A: At iteration 25429 the map still holds the reference on key. Size: 1 A: Key has finally been garbage collected at iteration 25430, hence the map is now empty. Size: 1 A: End.
結果可以明顯看出,沒有執行 Thread C 時,因為 Value Object 一直沒有被參考到,因此很快就被從 Map 中移除了。
參考資料:
1、Java's WeakHashMap and caching: Why is it referencing the keys, not the values?
2、guava-libraries
沒有留言:
張貼留言