原文地址:http://www.blogjava.net/DLevin/archive/2013/10/15/404770.html

前记:最近公司在做的项目完全基于Cache(Gemfire)构建了一个类数据库的系统,自己做的一个小项目里用过Guava的Cache,以前做过的项目中使用过EHCache,既然和Cache那么有缘,那就趁这个机会好好研究一下Java中的Cache库。在Java社区中已经提供了很多Cache库实现,具体可以参考http://www.open-open.com/13.htm,这里只关注自己用到的几个Cache库而且这几个库都比较具有代表性:Guava中提供的Cache是基于单JVM的简单实现;EHCache出自Hibernate,也是基于单JVM的实现,是对单JVM Cache比较完善的实现;而Gemfire则提供了对分布式Cache的完善实现。这一系列的文章主要关注在这几个Cache系统的实现上,因而步探讨关于Cache的好处、何时用Cache等问题,由于他们都是基于内存的Cache,因而也仅局限于这种类型的Cache(说实话,我不知道有没有其他的Cache系统,比如基于文件?囧)。

记得我最早接触Cache是在大学学计算机组成原理的时候,由于CPU的速度要远大于内存的读取速度,为了提高CPU的效率,CPU会在内部提供缓存区,该缓存区的读取速度和CPU的处理速度类似,CPU可以直接从缓存区中读取数据,从而解决CPU的处理速度和内存读取速度不匹配的问题。缓存之所以能解决这个问题是基于程序的局部性原理,即”程序在执行时呈现出局部性规律,即在一段时间内,整个程序的执行仅限于程序中的某一部分。相应地,执行所访问的存储空间也局限于某个内存区域。局部性原理又表现为:时间局部性和空间局部性。时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。空间局部性是指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问。”在实际工作中,CPU先向缓存区读取数据,如果缓存区已存在,则读取缓存中的数据(命中),否则(失效),将内存中相应数据块载入缓存中,以提高接下来的访问速度。由于成本和CPU大小的限制,CPU只能提供有限的缓存区,因而缓存区的大小是衡量CPU性能的重要指标之一。

使用缓存,在CPU向内存更新数据时需要处理一个问题(写回策略问题),即CPU在更新数据时只更新缓存的数据(write back,写回,当缓存需要被替换时才将缓存中更新的值写回内存),还是CPU在更新数据时同时更新缓存中和内存中的数据(write through,写通)。在写回策略中,为了减少内存写操作,缓存块通常还设有一个脏位(dirty bit),用以标识该块在被载入之后是否发生过更新。如果一个缓存块在被置换回内存之前从未被写入过,则可以免去回写操作;写回的优点是节省了大量的写操作。这主要是因为,对一个数据块内不同单元的更新仅需一次写操作即可完成。这种内存带宽上的节省进一步降低了能耗,因此颇适用于嵌入式系统。写通策略由于要经常和内存交互(有些CPU设计会在中间提供写缓冲器以缓解性能),因而性能较差,但是它实现简单,而且能简单的维持数据一致性。

在软件的缓存系统中,一般是为了解决内存的访问速率和磁盘、网络、数据库(属于磁盘或网络访问,单独列出来因为它的应用比较广泛)等访问速率不匹配的问题(对于内存缓存系统来说)。但是由于内存大小和成本的限制,我们又不能把所有的数据先加载进内存来。因而如CPU中的缓存,我们只能先将一部分数据保存在缓存中。此时,对于缓存,我们一般要解决如下需求:

  1. 使用给定Key从Cache中读取Value值。CPU是通过内存地址来定位内存已获取相应内存中的值,类似的在软件Cache中,需要通过某个Key值来标识相关的值。因而可以简单的认为软件中的Cache是一个存储键值对的Map,比如Gemfire中的Region就继承自Map,只是Cache的实现更加复杂。
  2. 当给定的Key在当前Cache不存在时,程序员可以通过指定相应的逻辑从其他源(如数据库、网络等源)中加载该Key对应的Value值,同时将该值返回。在CPU中,基于程序局部性原理,一般是默认的加载接下来的一段内存块,然而在软件中,不同的需求有不同的加载逻辑,因而需要用户自己指定对应的加载逻辑,而且一般来说也很难预知接下来要读取的数据,所以只能一次只加载一条纪录(对可预知的场景下当然可以批量加载数据,只是此时需要权衡当前操作的响应时间问题)。
  3. 可以向Cache中写入Key-Value键值对(新增的纪录或对原有的键值对的更新)。就像CPU的写回策略中有写回和写通策略,有些Cache系统提供了写通接口。如果没有提供写通接口,程序员需要额外的逻辑处理写通策略。也可以如CPU中的Cache一样,只当相应的键值对移出Cache以后,再将值写回到数据源,可以提供一个标记位以决定要不要写回(不过感觉这种实现比较复杂,代码的的耦合度也比较高,如果为提升写的速度,采用异步写回即可,为防止数据丢失,可以使用Queue来存储)。
  4. 将给定Key的键值对移出Cache(或给定多个Key以批量移除,甚至清除整个Cache)。
  5. 配置Cache的最大使用率,当Cache超过该使用率时,可配置溢出策略
    1. 直接移除溢出的键值对。在移除时决定是否要写回已更新的数据到数据源。
    2. 将溢出的溢出的键值对写到磁盘中。在写磁盘时需要解决如何序列化键值对,如何存储序列化后的数据到磁盘中,如何布局磁盘存储,如何解决磁盘碎片问题,如何从磁盘中找回相应的键值对,如何读取磁盘中的数据并方序列化,如何处理磁盘溢出等问题。
    3. 在溢出策略中,除了如何处理溢出的键值对问题,还需要处理如何选择溢出的键值对问题。这有点类似内存的页面置换算法(其实内存也可以看作是对磁盘的Cache),一般使用的算法有:先进先出(FIFO)、最近最少使用(LRU)、最少使用(LFU)、Clock置换(类LRU)、工作集等算法。
  6. 对Cache中的键值对,可以配置其生存时间,以处理某些键值对在长时间不被使用,但是又没能溢出的问题(因为溢出策略的选择或者Cache没有到溢出阶段),以提前释放内存。
  7. 对某些特定的键值对,我们希望它能一直留在内存中不被溢出,有些Cache系统提供PIN配置(动态或静态),以确保该键值对不会被溢出。
  8. 提供Cache状态、命中率等统计信息,如磁盘大小、Cache大小、平均查询时间、每秒查询次数、内存命中次数、磁盘命中次数等信息。
  9. 提供注册Cache相关的事件处理器,如Cache的创建、Cache的销毁、一条键值对的添加、一条键值对的更新、键值对溢出等事件。
  10. 由于引入Cache的目的就是为了提升程序的读写性能,而且一般Cache都需要在多线程环境下工作,因而在实现时一般需要保证线程安全,以及提供高效的读写性能。

在Java中,Map是最简单的Cache,为了高效的在多线程环境中使用,可以使用ConcurrentHashMap,这也正是我之前参与的一个项目中最开始的实现(后来引入EHCache)。为了语意更加清晰、保持接口的简单,下面我实现了一个基于Map的最简单的Cache系统,用以演示Cache的基本使用方式。用户可以向它提供数据、查询数据、判断给定Key的存在性、移除给定的Key(s)、清除整个Cache等操作。以下是Cache的接口定义。

 1 public interface Cache<K, V> {
 2     public String getName();
 3     public V get(K key);
 4     public Map<? extends K, ? extends V> getAll(Iterator<? extends K> keys);
 5     public boolean isPresent(K key);
 6     public void put(K key, V value);
 7     public void putAll(Map<? extends K, ? extends V> entries);
 8     public void invalidate(K key);
 9     public void invalidateAll(Iterator<? extends K> keys);
10     public void invalidateAll();
11     public boolean isEmpty();
12     public int size();
13     public void clear();
14     public Map<? extends K, ? extends V> asMap();
15 }

这个简单的Cache实现只是对HashMap的封装,之所以选择HashMap而不是ConcurrentHashMap是因为在ConcurrentHashMap无法实现getAll()方法;并且这里所有的操作我都加锁了,因而也不需要ConcurrentHashMap来保证线程安全问题;为了提升性能,我使用了读写锁,以提升并发查询性能。因为代码比较简单,所以把所有代码都贴上了(懒得整理了。。。。)。

public class CacheImpl<K, V> implements Cache<K, V> {private final String name;private final HashMap<K, V> cache;private final ReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock();private final Lock writeLock = lock.writeLock();public CacheImpl(String name) {this.name = name;cache = new HashMap<K, V>();}public CacheImpl(String name, int initialCapacity) {this.name = name;cache = new HashMap<K, V>(initialCapacity);}public String getName() {return name;}public V get(K key) {readLock.lock();try {return cache.get(key);} finally {readLock.unlock();}}public Map<? extends K, ? extends V> getAll(Iterator<? extends K> keys) {readLock.lock();try {Map<K, V> map = new HashMap<K, V>();List<K> noEntryKeys = new ArrayList<K>();while(keys.hasNext()) {K key = keys.next();if(isPresent(key)) {map.put(key, cache.get(key));} else {noEntryKeys.add(key);}}if(!noEntryKeys.isEmpty()) {throw new CacheEntriesNotExistException(this, noEntryKeys);}return map;} finally {readLock.unlock();}}public boolean isPresent(K key) {readLock.lock();try {return cache.containsKey(key);} finally {readLock.unlock();}}public void put(K key, V value) {writeLock.lock();try {cache.put(key, value);} finally {writeLock.unlock();}}public void putAll(Map<? extends K, ? extends V> entries) {writeLock.lock();try {cache.putAll(entries);} finally {writeLock.unlock();}}public void invalidate(K key) {writeLock.lock();try {if(!isPresent(key)) {throw new CacheEntryNotExistsException(this, key);}cache.remove(key);} finally {writeLock.unlock();}}public void invalidateAll(Iterator<? extends K> keys) {writeLock.lock();try {List<K> noEntryKeys = new ArrayList<K>();while(keys.hasNext()) {K key = keys.next();if(!isPresent(key)) {noEntryKeys.add(key);}}if(!noEntryKeys.isEmpty()) {throw new CacheEntriesNotExistException(this, noEntryKeys);}while(keys.hasNext()) {K key = keys.next();invalidate(key);}} finally {writeLock.unlock();}}public void invalidateAll() {writeLock.lock();try {cache.clear();} finally {writeLock.unlock();}}public int size() {readLock.lock();try {return cache.size();} finally {readLock.unlock();}}public void clear() {writeLock.lock();try {cache.clear();} finally {writeLock.unlock();}}public Map<? extends K, ? extends V> asMap() {readLock.lock();try {return new ConcurrentHashMap<K, V>(cache);} finally {readLock.unlock();}}public boolean isEmpty() {readLock.lock();try {return cache.isEmpty();} finally {readLock.unlock();}}}

其简单的使用用例如下:

 1     @Test
 2     public void testCacheSimpleUsage() {
 3         Book uml = bookFactory.createUMLDistilled();
 4         Book derivatives = bookFactory.createDerivatives();
 5
 6         String umlBookISBN = uml.getIsbn();
 7         String derivativesBookISBN = derivatives.getIsbn();
 8
 9         Cache<String, Book> cache = cacheFactory.create("book-cache");
10         cache.put(umlBookISBN, uml);
11         cache.put(derivativesBookISBN, derivatives);
12
13         Book fetchedBackUml = cache.get(umlBookISBN);
14         System.out.println(fetchedBackUml);
15
16         Book fetchedBackDerivatives = cache.get(derivativesBookISBN);
17         System.out.println(fetchedBackDerivatives);
18     }

转载于:https://www.cnblogs.com/qiluoao13077220/p/4160416.html

【转载】Java Cache系列之Cache概述和Simple Cache相关推荐

  1. Java 集合系列 16 HashSet

    java 集合系列目录: Java 集合系列 01 总体框架 Java 集合系列 02 Collection架构 Java 集合系列 03 ArrayList详细介绍(源码解析)和使用示例 Java ...

  2. Java专家系列:CPU Cache与高性能编程

    认识CPU Cache CPU Cache概述 随着CPU的频率不断提升,而内存的访问速度却没有质的突破,为了弥补访问内存的速度慢,充分发挥CPU的计算资源,提高CPU整体吞吐量,在CPU与内存之间引 ...

  3. 草稿--深度学习cache系列

    目录 <深度学习cache系列> - 2022 00-cache思考篇 01-简述cache的基本概念和使用场景 02-cache的基本概念原理扫盲 03-cache的查询原理 04-多核 ...

  4. Java 8系列之重新认识HashMap(转载自美团点评技术团队)

    Java 8系列之重新认识HashMap(转载自美团点评技术团队) 摘要 HashMap是Java程序员使用频率最高的用于映射(键值对)处理的数据类型.随着JDK(Java Developmet Ki ...

  5. Java缓存Ehcache-Ehcache的Cache预热机制及代码实现(Cache Warming for multi-tier Caches)

    Ehcache中Cache预热机制 Cache预热机制简介 Ehcache在程序启动的时候并不会立即去加载位于磁盘上的数据到内存,而是在数据被用到的时候去加载(lazy load).因此在cache启 ...

  6. java集合系列——java集合概述(一)

    在JDK中集合是很重要的,学习java那么一定要好好的去了解一下集合的源码以及一些集合实现的思想! 一:集合的UML类图(网上下载的图片) Java集合工具包位置是java.util.* 二:集合工具 ...

  7. cache系列——存储介质

    存储介质 一.存储介质 cache系列 存储介质 前言 一.存储介质是什么? 二.ROM 1.PROM 2.EPROM 3.EEPROM 三.RAM 1.DRAM 2.SRAM 四.FLASH 1.N ...

  8. Java多线程系列(一):最全面的Java多线程学习概述

    Java并发编程的技能基本涵括以下5方面: 多线程 线程池 线程锁 并发工具类 并发容器 多线程的4种创建方式 继承Thread 实现Runnable接口 实现Callable接口 以及线程池来创建线 ...

  9. [转载] 夯实Java基础系列8:深入理解Java内部类及其实现原理

    参考链接: Java内部类 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tut ...

最新文章

  1. Java 使用 Redis
  2. openStack 云平台管理节点管理网口流量非常大 出现丢包严重 终端总是时常中断问题调试及当前测试较有效方案...
  3. nodejs核心模块fs删除文件_用 NodeJS 重命名系统文件
  4. 消息幂等(去重)通用解决方案,RocketMQ
  5. ubuntu + vmware7.0 gmake not found
  6. SpringBoot:ApplicationEvent与ApplicationListener
  7. 港媒:中国将斥资1800亿美元建全球最大5G网络
  8. redis数据库实例
  9. 利用MQL5创建您自己的图形面板
  10. AI+护肤领域带来的产业价值
  11. 虹科OPC UA SDK案例:虹科OPC UA SDK助力立功科技ZWS云平台
  12. 用C语言程序求两个正整数的最大公约数
  13. 内部存储空间不足_手机提示存储空间不足的原因和解决方法
  14. 苹果a12_盘点那些你不知道的苹果a12和a10x的差距
  15. No module named ‘torchvision.models.feature_extraction‘
  16. 【转】高清混合矩阵应用于佛山市政府大礼堂会议系统解决方案
  17. java数组排序sort原理,ZooKeeper的十二连问
  18. 合并BPL包图文教程
  19. JavaSwing+MySQL+进销存管理系统
  20. 不到 20 人的互联网公司,该去吗?

热门文章

  1. Elasticsearch-7.0和Logstash-7.0和Kibaa-7.0的下载以及安装(百度网盘)
  2. python【数据结构与算法】二分模板
  3. php 长文本_php字符串太长怎么办
  4. 每个网站SEO优化人员都要熟知的三大图片优化技巧
  5. python语言1010的八进制_python打印十六进制
  6. 服务器cpu天梯图_九月手机处理器排名 2020年9月最新版手机CPU天梯图
  7. 别做喷子,多去钻研!
  8. Jupyter 快速入门——写python项目博客非常有用!!!
  9. Cython的简单使用
  10. intel python加速效果初探