Java Caching JSR107介绍(一)

来源:互联网 发布:矩阵的秩怎么求 编辑:程序博客网 时间:2024/06/06 03:20

Java Caching作为JSR107的规范已经在最终的制定中,我们的项目准备以JCache作为基础来扩展,因此这里参考JSR107规范文档进行简单的介绍。

1. 目标

  • 为应用程序提供缓存Java对象的功能。
  • 定义了一套通用的缓存概念和工具。
  • 最小化开发人员使用缓存的学习成本。
  • 最大化应用程序在使用不同缓存实现之间的可移植性。
  • 支持进程内和分布式的缓存实现。
  • 支持by-value和by-reference的缓存对象。
  • 定义遵照JSR-175的缓存注解;定义一套Java编程语言的元数据;

2. 核心概念

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry
CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
Entry是一个存储在Cache中的key-value对。
每一个存储在Cache中的条目有一个定义的有效期,即Expiry Duration。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

3. 一致性

一致性是指确保当并发缓存修改发生时,多线程访问缓存对该修改的可见性。

3.1 默认一致性

在默认一致性中,大多数缓存的操作类似于Cache存在对应key的锁机制。如果一个缓存操作在一个key上获得了一个排他的读或写锁,则在该key上后续的操作将被阻塞直到该锁被释放。其效果是当前线程执行的操作发生在另一个线程(包括不同Java虚拟机的线程)执行读或写之前。
这可以被理解为一种悲观锁定的方法,锁,修改和解锁。
一些Cache的操作的返回值是最近的值,这个值可能是旧值也可能是新值,这依赖于实现是返回哪个值。
这可以被理解为不保证一致性的无锁方式。
一些操作在当前状态匹配给定参数的情况下才会执行更新。多个线程调用这些方法可以自由地完成更新,就像它们共享一个锁。
●    boolean putIfAbsent(K key, V value);
●    boolean remove(K key, V oldValue);
●    boolean replace(K key, V oldValue, V newValue);
●    boolean replace(K key, V value);
●    V getAndReplace(K key, V value);
这可以理解为使用一个优化的锁方法,如果当前状态是正确的,则执行更新,否则失败。这被称为CAS,CPU指令也以这种方式操作。
这些方法必须与其它缓存操作交互就像他们有一个排它锁,如果他们没有获得一个排它锁,CAS方法不能写入新的值。
因此,默认一致性下CAS方法可以允许将由非CAS方法引起的更高水平的并发。

3.2 进一步的一致性模型

实现可能支持其它的一致性模型。
一些例子使用最近的值和一个显式的锁API。一个高并发的组合是读最近值并使用CAS方法来写。

4. 缓存拓扑

尽管规范不强制特定的缓存拓扑结构,但是缓存条目可能被存储在本地和/或分布在多个进程。实现可以选择不支持,支持其中一个或两个,或其他拓扑结构。
这个概念表现在规范的许多方面:
●大多数更新的方法有一个版本使用void或低成本的返回类型。虽然Map有V put(K key, V value)但Cache则只有void put(K key, V value)。
同时还提供更昂贵的返回类型版本。一个例子是缓存在V getAndPut(K键,V值)的方法,它像map一样返回旧值。
●创建不以进程内作为假设的语义。Configuration是可序列化的,这样就可以通过网络发送。开发者可以定义CacheEntryListener,ExpiryPolicy,CacheEntryFilter,CacheWriter和CacheLoader的实现,并与缓存相关联。为支持分布式拓扑结构,定义了一个Factory负责创建它们,而不是实例,该Factory接口是可序列化的。
●使用Iterable 作为方法的返回类型和参数可能会很大,该方法返回一个完整的集合,就像Map 的 keySet()方法,这种方法可能会产生问题。一个Cache可能如此之大,整个key的集合可能不适合都放入可用内存中,同时也可能造成很低效的网络传输。Cache中CacheEntryListener的子接口上的listener方法,以及CacheLoader和 CacheWriter上的批量方法都使用了Iterable。
●在接口CacheEntryListener, ExpiryPolicy, CacheEntryFilter, CacheWriter和CacheLoader的实例化和执行上没有做任何假设。
在一个分布式实现中这些对象可能都驻留在接近数据的一端,而不是在应用程序的进程中。
●CachingProvider.getCacheManager(URI uri, ClassLoader classLoader)返回一个与特定的ClassLoader和URI相关的CacheManager,可以实例化多个CacheManager实例。

5 执行上下文

EntryProcessors,CacheEntryListeners,CacheLoaders,CacheWriters和ExpiryPolicys被实例化在配置了指定URI和ClassLoader的CacheManager上下文中。意味着在接口的实例运行时必须能够访问在ClassLoader定义的应用程序类。
这一点意味着一个用户定义的EntryProcessor必须可以被使用它的缓存访问,如何访问依赖于其实现。例如,在分布式环境中,用户定义的类将被不同的ClassLoader实例加载,虽在运行的不同Java程序中,但能够访问相同的类。
在Java EE环境中,或通过增强的实现,以上接口的实例可以把其他的资源注入到这些实例方法的前后使用。

6 重入

虽然这个规范并没有限制开发者使用自定义EntryProcessors,CacheEntryListeners,CacheLoaders,CacheWriters和ExpiryPolicys中进行的操作,缓存的实现可以从这些接口限制重入。例如,一个实现可以限制EntryProcessors调用缓存方法的能力,或限制调用其他EntryProcessor。同样的实现可以限制CacheLoader/CacheWriter访问缓存的能力。
因此强烈建议开发人员避免写这些接口的可重入的实现,这些实现可能无法移植。

7. 一个简单的例子

这个简单例子创建一个默认的CacheManager, 配置一个使用String的key类型和Integer的值类型命名为“simpleCache”的缓存,过期时间为1小时,执行get和put操作。
    //获取CacheManager
    CachingProvider cachingProvider = Caching.getCachingProvider();
    CacheManager cacheManager = cachingProvider.getCacheManager();

    //配置缓存
    MutableConfiguration<String, Integer> config = 
   new MutableConfiguration<String, Integer>();
    config.setStoreByValue(false)
        .setTypes(String.class, Integer.class)
        .setExpiryPolicyFactory(AccessedExpiryPolicy.factoryOf(ONE_HOUR))
        .setStatisticsEnabled(true);

    //创建缓存
    cacheManager.createCache("simpleCache", config);

    //获取缓存
    Cache<String, Integer> cache = cacheManager.getCache("simpleCache",
        String.class, Integer.class);

    //缓存操作
    String key = "key";
    Integer value1 = 1;
    cache.put("key", value1);
    Integer value2 = cache.get(key);
    assertEquals(value1, value2);
    cache.remove(key);
    assertNull(cache.get(key));


这里使用默认的CachingProvider和默认的CacheManager,有一个静态的简便方法得到一个缓存,Caching.getCache:
    //获得缓存
    Cache<String, Integer> cache = Caching.getCache("simpleCache",
        String.class, Integer.class);  

原创粉丝点击