由于项目中一直使用的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 >()
.maximumWeightedCapacity (1000 )
.weigher (Weighers .singleton())
.build ();
1
2
3
4
5
//Map中的元素是set ,Weigher通过set 的size 来计算每一个元素的权重
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
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 )
.weigher (memoryUsageWeigher)
.build ();
由于每个元素的权重是在add的时候计算的, 如果元素的weight发生变化,那么要通过以下方式重新计算权重1
2
3
4
cache.put(key , value );
cache.replace(key , value );
cache.replace(key , value , value );
更多详细信息请参考项目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 ));
输出
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 )
.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 >() {
@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的维护团队更加活跃,一个简洁、活跃的开源项目总数比一个已经不经常更新的项目要好。