十二、Ehcache2.10.2源码分析
12.1 源码淘汰策略解析
首先看一下类结构图:

Paste_Image.png
从类结构图上看一共有三种缓存淘汰策略分别是:LFU,LRU,FIFO。关于这三个概念在前面都已经有过解释,我们直接这三个的源码:
1、LRUPolicy代码如下:
public class LruPolicy extends AbstractPolicy { /** * The name of this policy as a string literal */ public static final String NAME = "LRU"; /** * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO. */ public String getName() { return NAME; } /** * Compares the desirableness for eviction of two elements * * Compares hit counts. If both zero, * * @param element1 the element to compare against * @param element2 the element to compare * @return true if the second element is preferable to the first element for ths policy */ public boolean compare(Element element1, Element element2) { return element2.getLastAccessTime() < element1.getLastAccessTime(); }
注:
accessTime小的缓存淘汰。
2、LFUPolicy代码如下:
public class LfuPolicy extends AbstractPolicy { /** * The name of this policy as a string literal */ public static final String NAME = "LFU"; /** * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO. */ public String getName() { return NAME; } /** * Compares the desirableness for eviction of two elements * * Compares hit counts. If both zero, * * @param element1 the element to compare against * @param element2 the element to compare * @return true if the second element is preferable to the first element for ths policy */ public boolean compare(Element element1, Element element2) { return element2.getHitCount() < element1.getHitCount(); } }
注:
hit值小的缓存淘汰。
3、FIFOPolicy代码如下:
public class FifoPolicy extends AbstractPolicy { /** * The name of this policy as a string literal */ public static final String NAME = "FIFO"; /** * @return the name of the Policy. Inbuilt examples are LRU, LFU and FIFO. */ public String getName() { return NAME; } /** * Compares the desirableness for eviction of two elements * * Compares hit counts. If both zero, * * @param element1 the element to compare against * @param element2 the element to compare * @return true if the second element is preferable to the first element for ths policy */ public boolean compare(Element element1, Element element2) { return element2.getLatestOfCreationAndUpdateTime() < element1.getLatestOfCreationAndUpdateTime(); } }
注:
以creationAndUpdateTime最新或者最近的缓存淘汰。
4、这三个策略类统一继承AbstractPolicy抽类
最关键的就是下面这个方法:
public Element selectedBasedOnPolicy(Element[] sampledElements, Element justAdded) { //edge condition when Memory Store configured to size 0 if (sampledElements.length == 1) { return sampledElements[0]; } Element lowestElement = null; for (Element element : sampledElements) { if (element == null) { continue; } if (lowestElement == null) { if (!element.equals(justAdded)) { lowestElement = element; } } else if (compare(lowestElement, element) && !element.equals(justAdded)) { lowestElement = element; } } return lowestElement; }
注:
1、这个方法主要是从取样节点中查找需要淘汰的缓存。
2、最关键的就是调用compare这个方法其实就是调用的上面那三个策略实现的方法来找个可以淘汰的缓存节点。
那么接下来我们看一下淘汰缓存的生命周期流程是怎么样的。

Paste_Image.png
12.2 EhcacheManager类解析
这个类是2.10.2版本的最核心类,初始化、创建缓存、获取缓存都会用到这个类,这个类里面有几十个方法非常多,我们会按照类别分别进行介绍,先看其构造方法吧。

Paste_Image.png
先看方法CacheManager()默认的情况代码如下:
public CacheManager() throws CacheException { // default config will be done status = Status.STATUS_UNINITIALISED; init(null, null, null, null); }
Status.STATUS_UNINITIALISED这句的意思是缓存未被初始化,在构造方法里面要设定一个初始状态。
我们接着看init方法,这个方法是有别于其他构造方法的,因为默认的情况下这个方法的参数全传null值,这就意味着使用ehcache自己默认的配置了。
代码如下:
protected synchronized void init(Configuration initialConfiguration, String configurationFileName, URL configurationURL, InputStream configurationInputStream) { Configuration configuration; if (initialConfiguration == null) { configuration = parseConfiguration(configurationFileName, configurationURL, configurationInputStream); } else { configuration = initialConfiguration; } assertManagementRESTServiceConfigurationIsCorrect(configuration); assertNoCacheManagerExistsWithSameName(configuration); try { doInit(configuration); } catch (Throwable t) { if (terracottaClient != null) { terracottaClient.shutdown(); } if (statisticsExecutor != null) { statisticsExecutor.shutdown(); } if (featuresManager != null) { featuresManager.dispose(); } if (diskStorePathManager != null) { diskStorePathManager.releaseLock(); } if (cacheManagerTimer != null) { cacheManagerTimer.cancel(); cacheManagerTimer.purge(); } synchronized (CacheManager.class) { final String name = CACHE_MANAGERS_REVERSE_MAP.remove(this); CACHE_MANAGERS_MAP.remove(name); } ALL_CACHE_MANAGERS.remove(this); if (t instanceof CacheException) { throw (CacheException) t; } else { throw new CacheException(t); } } }
说明
1、首先要判断initialConfiguration这个参数是不是为空,判断的情况下肯定是为就直接调用了parseConfiguration这个方法,这个方法调用classpath默认路径来查找配置文件内容,初始化完configuration以后调用doInit方法。
2、doInit方法主要用来初始化最大的本地堆大小,初始化最大的本地持久化磁盘设置大小,集群模式,事务设置等等。
12.3 Cache类解析
cache的类继承结构如下所示:

Paste_Image.png
说明:
Ehcache接口是整个缓存的核心接口,该接口提供的方法可以直接操作缓存,比如put,get等方法。由于方法太多我们只单拿出来put和get方法做一个深入分析。
先看put方法源码:
private void putInternal(Element element, boolean doNotNotifyCacheReplicators, boolean useCacheWriter) { putObserver.begin(); if (useCacheWriter) { initialiseCacheWriterManager(true); } checkStatus(); if (disabled) { putObserver.end(PutOutcome.IGNORED); return; } if (element == null) { if (doNotNotifyCacheReplicators) { LOG.debug("Element from replicated put is null. This happens because the element is a SoftReference" + " and it has been collected. Increase heap memory on the JVM or set -Xms to be the same as " + "-Xmx to avoid this problem."); } putObserver.end(PutOutcome.IGNORED); return; } if (element.getObjectKey() == null) { putObserver.end(PutOutcome.IGNORED); return; } element.resetAccessStatistics(); applyDefaultsToElementWithoutLifespanSet(element); backOffIfDiskSpoolFull(); element.updateUpdateStatistics(); boolean elementExists = false; if (useCacheWriter) { boolean notifyListeners = true; try { elementExists = !compoundStore.putWithWriter(element, cacheWriterManager); } catch (StoreUpdateException e) { elementExists = e.isUpdate(); notifyListeners = configuration.getCacheWriterConfiguration().getNotifyListenersOnException(); RuntimeException cause = e.getCause(); if (cause instanceof CacheWriterManagerException) { throw ((CacheWriterManagerException)cause).getCause(); } throw cause; } finally { if (notifyListeners) { notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists); } } } else { elementExists = !compoundStore.put(element); notifyPutInternalListeners(element, doNotNotifyCacheReplicators, elementExists); } putObserver.end(elementExists ? PutOutcome.UPDATED : PutOutcome.ADDED); }
说明:
1、代码的逻辑其实很简单,我们看一下compoundStore这个类,实际上我们缓存的数据最终都是要到这个类里面进行存储的。
2、代码里面使用了putObserver观察者对象主要是用来做计数统计任务用的。
看一下compoundStore类的继承结构图如下:

Paste_Image.png
通过图中可以看到所有的存储类都实现Store接口类,大概有以下几种存储方式:
1、集群方式:ClusteredStore
2、缓存方式:CacheStore
3、内存方式:MemoryStore
4、磁盘方式:DiskStore
我们以DiskStore为例深入讲解磁盘的部分源码分析。
writeLock().lock(); try { // ensure capacity if (count + 1 > threshold) { rehash(); } HashEntry[] tab = table; int index = hash & (tab.length - 1); HashEntry first = tab[index]; HashEntry e = first; while (e != null && (e.hash != hash || !key.equals(e.key))) { e = e.next; } Element oldElement; if (e != null) { DiskSubstitute onDiskSubstitute = e.element; if (!onlyIfAbsent) { e.element = encoded; installed = true; oldElement = decode(onDiskSubstitute); free(onDiskSubstitute); final long existingHeapSize = onHeapPoolAccessor.delete(onDiskSubstitute.onHeapSize); LOG.debug("put updated, deleted {} on heap", existingHeapSize); if (onDiskSubstitute instanceof DiskStorageFactory.DiskMarker) { final long existingDiskSize = onDiskPoolAccessor.delete(((DiskStorageFactory.DiskMarker) onDiskSubstitute).getSize()); LOG.debug("put updated, deleted {} on disk", existingDiskSize); } e.faulted.set(faulted); cacheEventNotificationService.notifyElementUpdatedOrdered(oldElement, element); } else { oldElement = decode(onDiskSubstitute); free(encoded); final long outgoingHeapSize = onHeapPoolAccessor.delete(encoded.onHeapSize); LOG.debug("put if absent failed, deleted {} on heap", outgoingHeapSize); } } else { oldElement = null; ++modCount; tab[index] = new HashEntry(key, hash, first, encoded, new AtomicBoolean(faulted)); installed = true; // write-volatile count = count + 1; cacheEventNotificationService.notifyElementPutOrdered(element); } return oldElement; } finally { writeLock().unlock(); if (installed) { encoded.installed(); } }
说明:
1、流程采用写锁,先将这段代码锁定。
2、程序中有HashEntry[] tab这样一个桶,每个桶中存储一个链表,首先通过hash & (tab -1) 也就是key的hash值与桶的长度减1取余得出一个桶的index。然后取出链表实体,得到当前链表实体的下一个元素,如果元素为null则直接将元素赋值,否则取出旧的元素用新元素替换,释放旧元素空间,返回旧元素。




