将ReadWriteLock应用于缓存设计

2024-02-21 21:08

本文主要是介绍将ReadWriteLock应用于缓存设计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 --针对缓慢变化的小数据的缓存实现模型

 

 

    在JavaEEdev站点(http://www.javaeedev.com )的设计中,有几类数据是极少变化的,如ArticleCategory(文档分类),ResourceCategory(资源分类),Board(论坛版面)。在对应的DAO实现中,总是一次性取出所有的数据,例如:

List<ArticleCategory>  getArticleCategories();

  此类数据的特点是:数据量很小,读取非常频繁,变化却极慢(几天甚至几十天才变化一次),如果每次通过DAO从数据库获取数据,则增加了数据库 服务器的压力。为了在不影响整个系统结构的情况下透明地缓存这些数据,可以在Facade一层通过Proxy模式配合ReadWriteLock实现缓 存,而客户端和后台的DAO数据访问对象都不受影响:

将ReadWriteLock应用于缓存设计

  首先,现有的中间层是由Facade接口和一个FacadeImpl具体实现构成的。对ArticleCategory的相关操作在FacadeImpl中实现如下:

public class FacadeImpl implements Facade {protected CategoryDao categoryDao;public void setCategoryDao(CategoryDao categoryDao) {this.categoryDao = categoryDao;}// 读操作:public ArticleCategory queryArticleCategory(Serializable id) {return categoryDao.queryArticleCategory(id);}// 读操作:public List<ArticleCategory> queryArticleCategories() {return categoryDao.queryArticleCategories();}// 写操作:public void createArticleCategory(ArticleCategory category) {categoryDao.create(category);}// 写操作:public void deleteArticleCategory(ArticleCategory category) {categoryDao.delete(category);}// 写操作:public void updateArticleCategory(ArticleCategory category) {categoryDao.update(category);}// 其他方法省略...
}

  设计代理类FacadeCacheProxy,让其实现缓存ArticleCategory的功能:

public class FacadeCacheProxy implements Facade {private Facade target;public void setFacadeTarget(Facade target) {this.target = target;}// 定义缓存对象:private FullCache<ArticleCategory> cache = new FullCache<ArticleCategory>() {// how to get real data when cache is unavailable:protected List<ArticleCategory> doGetList() {return target.queryArticleCategories();}};// 从缓存返回数据:public List<ArticleCategory> queryArticleCategories() {return cache.getCachedList();}// 创建新的ArticleCategory后,让缓存失效:public void createArticleCategory(ArticleCategory category) {target.createArticleCategory(category);cache.clearCache();}// 更新某个ArticleCategory后,让缓存失效:public void updateArticleCategory(ArticleCategory category) {target.updateArticleCategory(category);cache.clearCache();}// 删除某个ArticleCategory后,让缓存失效:public void deleteArticleCategory(ArticleCategory category) {target.deleteArticleCategory(category);cache.clearCache();}
}

  该代理类的核心是调用读方法getArticleCategories()时,直接从缓存对象FullCache中返回结果,当调用写方法(create,update和delete)时,除了调用target对象的相应方法外,再将缓存对象清空。

  FullCache便是实现缓存的关键类。为了实现强类型的缓存,采用泛型实现FullCache:

public abstract class FullCache<T extends AbstractId> {...
}

  AbstractId是所有Domain Object的超类,目的是提供一个String类型的主键,同时便于在Hibernate或其他ORM框架中只需要配置一次JPA注解:

@MappedSuperclass
public abstract class AbstractId {protected String id;@Id@Column(nullable=false, updatable=false, length=32)@GeneratedValue(generator="system-uuid")@GenericGenerator(name="system-uuid", strategy="uuid")public String getId() { return id; }public void setId(String id) { this.id = id; }
}

  FullCache实现以下2个功能:

  1. List<T> getCachedList():获取整个缓存的List<T>
  2. clearCache():清除所有缓存

  此外,FullCache在缓存失效的情况下,必须从真正的数据源获得数据,因此,抽象方法:

  protected abstract List<T> doGetList()

  负责获取真正的数据。

  下面,用ReadWriteLock实现该缓存模型。

  Java 5平台新增了java.util.concurrent包,该包包含了许多非常有用的多线程应用类,例如ReadWriteLock,这使得开发人员不必自己封装就可以直接使用这些健壮的多线程类。

  ReadWriteLock是一种常见的多线程设计模式。当多个线程同时访问同一资源时,通常,并行读取是允许的,但是,任一时刻只允许最多一个线程写入,从而保证了读写操作的完整性。下图很好地说明了ReadWriteLock的读写并发模型:

 

允许不允许
不允许不允许

 

 

 

 

      当读线程远多于写线程时,使用ReadWriteLock来取代synchronized同步会显著地提高性能,因为大多数时候,并发的多个读线程不需要等待。

  Java 5的ReadWriteLock接口仅定义了如何获取ReadLock和WriteLock的方法,对于具体的ReadWriteLock的实现模式并没 有规定,例如,Read优先还是Write优先,是否允许在等待写锁的时候获取读锁,是否允许将一个写锁降级为读锁,等等。

  Java 5自身提供的一个ReadWriteLock的实现是ReentrantReadWriteLock,该ReadWriteLock实现能满足绝大多数的多线程环境,有如下特点:

  1. 支持两种优先级模式,以时间顺序获取锁和以读、写交替优先获取锁的模式;
  2. 当获得了读锁或写锁后,还可重复获取读锁或写锁,即ReentrantLock;
  3. 获得写锁后还可获得读锁,但获得读锁后不可获得写锁;
  4. 支持将写锁降级为读锁,但反之不行;
  5. 支持在等待锁的过程中中断;
  6. 对写锁支持Condition(用于取代wait,notify和notifyAll);
  7. 支持锁的状态检测,但仅仅用于监控系统状态而并非同步控制;

  FullCache采用ReentrantReadWriteLock实现读写同步:

public abstract class FullCache<T extends AbstractId> {private final ReadWriteLock lock = new ReentrantReadWriteLock();private final Lock readLock = lock.readLock(); // 读锁private final Lock writeLock = lock.writeLock(); // 写锁private List<T> cachedList = null; // 持有缓存的数据,若为null,表示缓存失效
}

  对于clearCache()方法,由于其是一个写操作,故定义如下:

public void clearCache() {writeLock.lock();cachedList = null;writeLock.unlock();
}

  对于get方法,由于是读操作,同时要考虑在缓存失效的情况下更新数据,其实现就稍微复杂一点:

public List<T> getCachedList() {// 获得读锁:readLock.lock();try {if(cachedList==null) {// 在获得写锁前,必须先释放读锁:readLock.unlock();writeLock.lock();try {cachedList = doGetList(); // 获取真正的数据}finally {// 在释放写锁前,先获得读锁:readLock.lock();writeLock.unlock();}}return cachedList;}finally {// 确保读锁在方法返回前被释放:readLock.unlock();}
}

  通过适当的装配(例如在Spring IoC容器中),让客户端持有FacadeCacheProxy的引用,就在中间层完全实现了透明的缓存,客户端代码一行也不用更改。

  考虑到多线程模型远比单线程复杂,为了确保FullCache实现的健壮性,编写一个FullCacheTest来执行单元测试:

public class FullCacheTest {// count how many hits:class Hit {private AtomicInteger total = new AtomicInteger(0);private AtomicInteger notHit = new AtomicInteger(0);public void total() {total.incrementAndGet();}public void notHit() {notHit.incrementAndGet();}public void debug() {System.err.println("Total get: " + total.intValue());System.err.println("Not hit: " + notHit.intValue());System.err.println("Hits: " + ((total.intValue()-notHit.intValue()) * 100 / total.intValue()) + "%");}}private static final int DATA_OPERATION = 10;private static final int MAX = 10;private static String[] ids = new String[MAX];static {for(int i=0; i<MAX; i++) {ids[i] = UUID.randomUUID().toString();}}private Hit hit;private FullCache<ArticleCategory> cache;@Beforepublic void setUp() {hit = new Hit();cache = new FullCache<ArticleCategory>() {@Overrideprotected List<ArticleCategory> doGetList() {hit.notHit();List<ArticleCategory> list = new ArrayList<ArticleCategory>();for(int i=0; i<MAX; i++) {ArticleCategory obj = new ArticleCategory();obj.setId(ids[i]);list.add(obj);}doSleep(DATA_OPERATION);return list;}@Overridepublic List<ArticleCategory> getCachedList() {hit.total();return super.getCachedList();}};}@Testpublic void testMultiThread() {final int THREADS = 100;final int LOOP_PER_THREAD = 100000;List<Thread> threads = new ArrayList<Thread>(THREADS);// test FullCache.getCachedList(id):for(int i=0; i<THREADS; i++) {threads.add(new Thread() {public void run() {for(int j=0; j<LOOP_PER_THREAD; j++) {List<ArticleCategory> list = cache.getCachedList();for(int k=0; k<MAX; k++) {assertEquals(ids[k], list.get(k).getId());}}}});}// test FullCache.clearCache():Thread clearThread = new Thread() {public void run() {for(;;) {cache.clearCache();try {Thread.sleep(DATA_OPERATION * 2);}catch(InterruptedException e) {break;}}}};// start all threads:clearThread.start();for(Thread t : threads) {t.start();}// wait for all threads:for(Thread t : threads) {try {t.join();}catch(InterruptedException e) {}}clearThread.interrupt();try {clearThread.join();}catch(InterruptedException e) {}// statistics:hit.debug();}private static void doSleep(long n) {try {Thread.sleep(n);}catch(InterruptedException e) {}}
}

  反复运行JUnit测试,均未报错。统计结果如下:

  Total get: 10000000

  Not hit: 7

  Hits: 99%

  执行时间3.9秒。如果用synchronized取代ReadWriteLock,执行时间为204秒,可见性能差异巨大。

总结:

  接口和实现的分离是必要的,否则难以实现Proxy模式。

  Facade模式和DAO模式都是必要的,否则,一旦数据访问分散在各个Servlet或JSP中,将难以控制缓存读写。

  下载完整的源代码

 作者简介
廖雪峰 (dev2dev id: xuefengl ),长期从事J2EE/J2ME开发,对Open Source框架有深入研究,曾参与网易商城等大型J2EE应用的开发。目前廖雪峰创建了JavaEE开发网(http://www.javaeedev.com ),著有《Spring 2.0核心技术与最佳实践》一书。

这篇关于将ReadWriteLock应用于缓存设计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/733073

相关文章

Python结合PyWebView库打造跨平台桌面应用

《Python结合PyWebView库打造跨平台桌面应用》随着Web技术的发展,将HTML/CSS/JavaScript与Python结合构建桌面应用成为可能,本文将系统讲解如何使用PyWebView... 目录一、技术原理与优势分析1.1 架构原理1.2 核心优势二、开发环境搭建2.1 安装依赖2.2 验

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

SpringShell命令行之交互式Shell应用开发方式

《SpringShell命令行之交互式Shell应用开发方式》本文将深入探讨SpringShell的核心特性、实现方式及应用场景,帮助开发者掌握这一强大工具,具有很好的参考价值,希望对大家有所帮助,如... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

MySQL 分区与分库分表策略应用小结

《MySQL分区与分库分表策略应用小结》在大数据量、复杂查询和高并发的应用场景下,单一数据库往往难以满足性能和扩展性的要求,本文将详细介绍这两种策略的基本概念、实现方法及优缺点,并通过实际案例展示如... 目录mysql 分区与分库分表策略1. 数据库水平拆分的背景2. MySQL 分区策略2.1 分区概念

Spring Shell 命令行实现交互式Shell应用开发

《SpringShell命令行实现交互式Shell应用开发》本文主要介绍了SpringShell命令行实现交互式Shell应用开发,能够帮助开发者快速构建功能丰富的命令行应用程序,具有一定的参考价... 目录引言一、Spring Shell概述二、创建命令类三、命令参数处理四、命令分组与帮助系统五、自定义S

C语言函数递归实际应用举例详解

《C语言函数递归实际应用举例详解》程序调用自身的编程技巧称为递归,递归做为一种算法在程序设计语言中广泛应用,:本文主要介绍C语言函数递归实际应用举例的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录前言一、递归的概念与思想二、递归的限制条件 三、递归的实际应用举例(一)求 n 的阶乘(二)顺序打印

Python中随机休眠技术原理与应用详解

《Python中随机休眠技术原理与应用详解》在编程中,让程序暂停执行特定时间是常见需求,当需要引入不确定性时,随机休眠就成为关键技巧,下面我们就来看看Python中随机休眠技术的具体实现与应用吧... 目录引言一、实现原理与基础方法1.1 核心函数解析1.2 基础实现模板1.3 整数版实现二、典型应用场景2

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda