2013年6月10日 星期一

利用 Guava 的 MapMaker 產生 weak value 版的 WeakHashMap

續前篇「WeakHashMap 的使用
目標是想要有個 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

沒有留言: