并发读写缓存实现机制(一):为什么ConcurrentHashMap可以这么快?
大家都知道ConcurrentHashMap的并发读写速度很快,但为什么它会这么快?这主要归功于其内部数据结构和独特的hash运算以及分离锁的机制。做游戏性能很重要,为了提高数据的读写速度,方法之一就是采用缓存机制。因此缓存的性能直接影响游戏的承载量和运行流畅度,作为核心基础设施,缓存必须具备以下方面的功能:
1.快速定位数据 2.并发变更数据 3.数据的过期控制与异步写入 4.高并发的情况下缓存数据的一致性 接下来,我就就几篇文章从上述几个方面来讲述下单服务器的缓存实现原理,本文的缓存是在guava的Cache基础上进一步扩展,原google缓存文档可参考:http://code.google.com/p/guava-libraries/wiki/CachesExplained 注意:本文是guava的Cache增强版,因此源码有稍许改动,详细源码请参考:https://github.com/cm4j/cm4j-all。 系列文章目录: 并发读写缓存实现机制(零):缓存操作指南 并发读写缓存实现机制(一):为什么ConcurrentHashMap可以这么快? 并发读写缓存实现机制(二):高并发下数据写入与过期 并发读写缓存实现机制(三):API封装和简化1.ConcurrentHashMap的数据结构
我们知道,一本书有着丰富的内容,那如何从一本书中找到我所需要的主要内容呢?自然而然我们就想到目录和子目录,首先,目录把书的内容分成很多个小块;其次,目录也是一个索引,通过目录我们就知道对应内容位于这本书的第几页,然后我们再按顺序浏览就能找到我们所需要的文章内容。 google的Cache借鉴了JDK的ConcurrentHashMap的设计思路,其本质就是基于上述流程设计的,翻看两者源码,有很大一部分是相同的,为了更好的理解缓存的高并发的实现,我们先来探索下ConcurrentHashMap的数据结构图: 由上图我们可以看出,首先ConcurrentHashMap先把数据分到0-16个默认创建好的数组中,数组里面的元素就叫segment,相当于书的大目录;每个segment里面包含一个名叫table的数组,这个数组里面的元素就是HashEntry,相当于书的一个子目录;HashEntry里面有下一个HashEntry的引用,这样一个一个迭代就能找到我们所需要的内容。 ConcurrentHashMap 类中包含两个静态内部类HashEntry和Segment。HashEntry用来封装映射表的 键/值对;Segment 用来充当数据划分和锁的角色,每个Segment对象守护整个散列映射表的若干个table。每个table是由若干个 HashEntry对象链接起来的链表。一个ConcurrentHashMap实例中包含由若干个Segment对象组成的数组。 a.HashEntry 清单1:HashEntry的定义
1 2 3 4 5 6 |
static final class HashEntry final K key; final int hash; volatile AbsReference value; final HashEntry } |
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 |
AbsReference get(String key, int hash, CacheLoader<String, AbsReference> loader, boolean isLoad) { final StopWatch watch = new Slf4JStopWatch(); try { if (count != 0) { // 先看看数量是否大于0 HashEntry e = getEntry(key, hash); if (e != null) { // 这里只是一次无锁情况的快速尝试查询,如果未查询到,会在有锁情况下再查一次 AbsReference value = getLiveValue(key, hash, now()); watch.lap("cache.getLiveValue()"); if (value != null) { recordAccess(e); return value; } } } if (isLoad) { // 对象为null或者对象已过期,则从在锁的情况下再查一次,还没有则从DB中加载数据 AbsReference ref = lockedGetOrLoad(key, hash, loader); watch.lap("cache.lockedGetOrLoad()"); return ref; } } finally { postReadCleanup(); watch.stop("cache.get()"); } return null; } |
1 2 3 4 5 6 7 8 |
HashEntry getEntry(String key, int hash) { // 首先拿到链头HashEntry,然后依次查找整个entry链 for (HashEntry e = getFirst(hash); e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { return e; } } return null; } |
1 2 3 4 |
HashEntry AtomicReferenceArray return tab.get(hash & (tab.length() - 1)); } |