由于项目中一直使用的Google的ConcurrentLinkedHashMap作为JVM缓存的容器,该项目已经很久没有更新,从作者的官方文档中看到ConcurrentLinkedHashMap已经被合并到了Guava包中,推荐使用Guava的MapMaker和CacheBuilder,本文稍微简述一下两者的用法。

ConcurrentLinkedHashMap

提供一个基于权重管理容量的Map,有以下特性

  • 基于LRU(Lateast recently use)算法来替换Map中的元素
  • 再高负载情况下,和ConrrentHashMap具有相同的性能
  • Can bound by the size of the values (e.g. Multimap cache) (这条没看懂)
  • 提供元素移除的通知事件
1
2
3
4
5
6
7
ConcurrentMap<K, V> cache = new ConcurrentLinkedHashMap.Builder<K, V>()
//设置Map的最大容量(权重)。
//如果Map中每个元素的权重都是1的话,那么就和普通HashMap的容量是一个意思。
//例如:A元素权重为1,B元素权重为2,那么A+B总权重是3,那么相当于占了3个容量。
.maximumWeightedCapacity(1000)
.weigher(Weighers.singleton()) //设置权重管理器,每个元素权重为1
.build();
1
2
3
4
5
//Map中的元素是set,Weigher通过setsize来计算每一个元素的权重
ConcurrentMap<Vertex, Set<Edge>> graphCache = new ConcurrentLinkedHashMap.Builder<Vertex, Set<Edge>>()
.maximumWeightedCapacity(1000)
.weigher(Weighers.<Edge>set())
.build();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//以内存作为最大权重的Map
EntryWeigher<K, V> memoryUsageWeigher = new EntryWeigher<K, V>() {
final MemoryMeter meter = new MemoryMeter();
//计算该元素的内存大小
@Override public int weightOf(K key, V value) {
long bytes = meter.measure(key) + meter.measure(value);
return (int) Math.min(bytes, Integer.MAX_VALUE);
}
};
ConcurrentMap<K, V> cache = new ConcurrentLinkedHashMap.Builder<K, V>()
.maximumWeightedCapacity(1024 * 1024) // 1 MB 最高内存
.weigher(memoryUsageWeigher)//指定权重计算器
.build();

由于每个元素的权重是在add的时候计算的, 如果元素的weight发生变化,那么要通过以下方式重新计算权重

1
2
3
4
// Inform the cache that the value's weight has changed, pick one of:
cache.put(key, value); // add or replace the entry
cache.replace(key, value); // replace if the entry exists
cache.replace(key, value, value); // only replace if the same instance

更多详细信息请参考项目githup页面 https://github.com/ben-manes/concurrentlinkedhashmap

Guava

ConcurrentLinkedHashMap作者将ConcurrentLinkedHashMap包含进了Guava项目,并且提供了跟友好和一用的接口,下面是作者原话:

Guava is the long term replacement and most of the time you should use it. The history is that ConcurrentLinkedHashMap figured out the algorithms, Guava subsumed it, and then focused on adding features.

在Guava里面作者将Map和Cache分开成两个

  • MapMaker 用于构建一个可以自动将key或者value包装成WeakRefrence的ConcurrentMap,并且效率和ConcurrentMap相当
1
2
3
4
5
6
7
ConcurrentMap<String, Integer> map = new MapMaker().weakKeys().makeMap();
String key = "key1";
map.put(key, 1);
key = null;
System.gc();
Thread.sleep(1000);
System.out.println("key->" + map.get(key));

输出

1
key->null

  • CacheBuilder 用于构建一个缓存,改缓存具有自动加载、缓存回收等功能
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
Cache<String, Integer> cache = CacheBuilder.newBuilder()
.concurrencyLevel(20)//设置并发等级,即最高并发
.maximumWeight(3)//设置最大权重(这里基于权重的缓存回收,比如配套Wigher)
.removalListener(new RemovalListener<String, Integer>() {//元素被回收的监听器
@Override
public void onRemoval(RemovalNotification<String, Integer> notification) {
System.out.println(notification.getkey() + "被移除,原因:" + notification.getCause());
}
}).weigher(new Weigher<String, Integer>() {//设置Wigher,用于计算每个元素的权重
@Override
public int weigh(String key, Integer value) {
return 1;
}
}).build();
cache.put("1", 1);
cache.put("2", 2);
cache.put("3", 3);
cache.getIfPresent("1");
cache.put("4", 4);
System.out.println(cache.asMap());
cache.invalidate("4");
System.out.println(cache.asMap());

输出

1
2
3
4
2 被移除,原因:SIZE
{3=3, 4=4, 1=1}
4 被移除,原因:EXPLICIT
{3=3, 1=1}

更多详细信息请参考项目githup页面 https://github.com/google/guava

总结

正如作者推荐的,在大部分情况下我们应该使用Guava中的CacheBuilder来替代ConcurrentLinkedHashMap,因为基于同样的算法,同样效率,CacheBuilder具有更简洁易用API,并且Guava的维护团队更加活跃,一个简洁、活跃的开源项目总数比一个已经不经常更新的项目要好。