目標是想要有個 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)。
範例程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
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 裡。
執行結果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
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 的話,結果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
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
沒有留言:
張貼留言