- 浏览: 2869742 次
- 性别:
- 来自: 武汉
文章分类
- 全部博客 (1173)
- 名言警句 (5)
- 心情随笔 (50)
- 数据库 (57)
- Java基础 (241)
- J2EE框架 (91)
- 数据结构 (12)
- 程序设计 (21)
- WEB技术 (128)
- 网络日志 (12)
- IT资讯 (247)
- linux (64)
- solaris (2)
- 其它 (143)
- WebService (4)
- 日语学习 (2)
- 机器人 (5)
- Android (5)
- cgywin (3)
- Game (1)
- DWR (1)
- spring (8)
- canvas (1)
- Guava (3)
- Modbus (5)
- 测试 (6)
- mongodb (9)
- Quartz (2)
- Cron (1)
- windows (2)
- 持续集成 (1)
- bootstrap (3)
- 结对编程 (1)
- nodejs (1)
- Netty (1)
- 安全 (3)
- webstorm (2)
- sparkline (1)
- Job (1)
- git (3)
- Maven (3)
- knockout (5)
- jquery (1)
- bower (1)
- docker (1)
- confluence (4)
- wiki (1)
- GoogleMap (1)
- jekyll (10)
- ruby (2)
- npm (3)
- browserify (1)
- gulp (3)
- openwrt (1)
- discuz (3)
- 输入法 (1)
- JPA (1)
- eclipse (2)
- IntelliJ (1)
- css (1)
- 虚拟机 (1)
- 操作系统 (1)
- azkaban (2)
- scrum (1)
最新评论
-
pangxiea_:
你好, 想请问一下 Linux下 这么使用rxtxcomm 在 ...
使用Java进行串口通信 -
abababudei:
请教一下,这个您是怎么解决的:/dev/ttyS2enteri ...
Java应用程序的MODBUS通讯 -
xuniverse:
hannibal005 写道楼主,我问下 request.se ...
用javascript与java进行RSA加密与解密 -
atxkm:
找了一下午,终于找到了
gulp 拷贝文件时如何移除文件目录结构 -
kalogen:
gtczr 写道非常感谢,经过我自己的修改,已经完美实现。发出 ...
用javascript与java进行RSA加密与解密
--针对缓慢变化的小数据的缓存实现模型
在JavaEEdev站点(http://www.javaeedev.com )的设计中,有几类数据是极少变化的,如ArticleCategory(文档分类),ResourceCategory(资源分类),Board(论坛版面)。在对应的DAO实现中,总是一次性取出所有的数据,例如:
List<ArticleCategory> getArticleCategories();
此类数据的特点是:数据量很小,读取非常频繁,变化却极慢(几天甚至几十天才变化一次),如果每次通过DAO从数据库获取数据,则增加了数据库 服务器的压力。为了在不影响整个系统结构的情况下透明地缓存这些数据,可以在Facade一层通过Proxy模式配合ReadWriteLock实现缓 存,而客户端和后台的DAO数据访问对象都不受影响:
首先,现有的中间层是由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个功能:
- List<T> getCachedList():获取整个缓存的List<T>
- 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实现能满足绝大多数的多线程环境,有如下特点:
- 支持两种优先级模式,以时间顺序获取锁和以读、写交替优先获取锁的模式;
- 当获得了读锁或写锁后,还可重复获取读锁或写锁,即ReentrantLock;
- 获得写锁后还可获得读锁,但获得读锁后不可获得写锁;
- 支持将写锁降级为读锁,但反之不行;
- 支持在等待锁的过程中中断;
- 对写锁支持Condition(用于取代wait,notify和notifyAll);
- 支持锁的状态检测,但仅仅用于监控系统状态而并非同步控制;
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; @Before public void setUp() { hit = new Hit(); cache = new FullCache<ArticleCategory>() { @Override protected 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; } @Override public List<ArticleCategory> getCachedList() { hit.total(); return super.getCachedList(); } }; } @Test public 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核心技术与最佳实践》一书。 |
评论
有漏洞
finally {
// 在释放写锁前,先获得读锁:
readLock.lock();
writeLock.unlock();
}
为什么要先获取读锁,还望指教下
发表评论
-
spring mvc @controller unit test
2013-11-20 14:14 38861. spring mvc controller imple ... -
Tomcat 生产服务器性能优化
2013-07-23 06:45 1007试想以下这个情景:你已经开发好了一个程序,这个程序的排版很不 ... -
Loading Multiple Spring Application Contexts with their own ClassLoader
2013-07-11 20:45 1072package de.incompleteco.spring ... -
ActiveMQ JMS的测试
2011-04-06 18:15 3918有二种方式可以测试。 1. 透过testng, 在测试类中完 ... -
Maven系列2--pom.xml 配置详解
2011-01-06 18:00 3176<project xmlns="http ... -
fisheye2.3.6 安装笔记
2010-09-16 20:25 2234一. 准备工作 1. 下载fisheye ... -
服务器端编程的十大性能问题
2010-09-15 12:01 1233今年5 月底,瑞士计算 ... -
JMX in spring 配置
2010-09-07 12:00 3050JConsole中的连接:简单的localhost:1099 ... -
Ehcache 2.0:后写式缓存和JTA支持
2010-05-22 11:13 1854开源缓存框架Ehcache 最 ... -
Twitter系统运维经验
2010-04-12 22:24 1551最近看到的另外一个 ... -
采用OSGi框架开发项目的十个问题
2010-03-21 18:08 1462近期,InfoQ针对Java模块 ... -
IBM和Eclipse加大对OSGi的支持
2010-03-21 18:05 1272为Java提供模块性的OSGi,正在受到IBM和Eclipse ... -
Castor功能与应用参考
2010-03-12 12:48 36271. 项目简介Castor是一个开源的Java项目 ... -
hibernate之 DetachedCriteria实现多表查询
2009-07-25 17:13 13092DetachedCriteria detachedCriter ... -
spring AOP 理论知识点总结
2009-07-24 17:47 21741.1 AOP 的概念 ... -
web.xml 中的listener、 filter、servlet 加载顺序及其详解
2009-07-02 16:14 4122在项目中总会遇到一些 ... -
maven笔记
2009-06-22 22:14 1529mvn package : 项目打包 mvn help:ef ... -
利用maven构建多模块项目
2009-06-17 14:28 53611. 先单独构建各模块为一个独立的项目。 jar项目: m ... -
Maven基础
2009-06-17 12:58 1859下载Maven安装文件。 定义环境变量M2_HOME到pat ... -
安装M2eclipse步骤
2009-06-15 14:45 38221.下载下列所需文件 eclipse3.4.2 ...
相关推荐
此外,我们还需要考虑缓存的并发控制,如`ReadWriteLock`、`StampedLock`等,以及缓存穿透、缓存雪崩和缓存击穿等问题,这些问题在设计缓存时都需要进行预防和处理。 最后,对于大型系统,监控和度量缓存性能也至关...
在实际应用中,ReadWriteLock常用于数据库连接池、缓存等读多写少的场景。需要注意的是,使用ReadWriteLock时,正确地管理和释放锁至关重要,避免出现死锁或资源泄漏的问题。此外,为了评估ReadWriteLock对性能的...
一级缓存是基于PerpetualCache的HashMap本地缓存,它的存储范围局限于Session。这意味着在一次Session中,如果相同的查询被执行多次,MyBatis会从缓存中获取结果,而不是每次都去数据库查询。然而,当Session被flush...
本篇文章将深入探讨相关知识点,包括并发基础、线程安全、同步机制、并发设计模式以及性能优化策略。 1. 并发基础:并发是指多个执行单元(如线程或进程)在同一时间段内执行,但并不意味着它们是并行的。在单核CPU...
Java LocalCache 本地缓存的实现实例主要应用于Java应用中,对于访问频率高,更新少的数据,通常的方案是将这类数据加入缓存中。相对从数据库中读取来说,读缓存效率会有很大提升。在集群环境下,常用的分布式缓存有...
面试时可能会考察如何设计缓存策略、缓存穿透、缓存雪崩和缓存更新策略等问题。 【锁】 在多线程环境下,锁用于控制并发访问共享资源,确保数据的一致性。Java提供了多种锁机制,如synchronized关键字、...
《美团系统交易面试知识点详解》 在IT行业中,面试是检验技术能力的重要环节,特别是对于美团这样的大型互联网公司,其...通过深入学习和实践这些知识点,将有助于提升面试表现,从而在竞争激烈的IT行业中脱颖而出。
8. 缓存技术:包括缓存的应用场景,缓存加载策略与失效策略,缓存与数据库同步等,以及缓存预热、缓存失效、缓存击穿、缓存雪崩、多级缓存、缓存与Spring+ORM框架集成等。 9. 消息队列技术:需要掌握消息队列的基本...
- 23种设计模式:单例、工厂、观察者、装饰者、适配器、代理等模式的理解与应用。 8. **算法** - 常见算法:排序(冒泡、插入、选择、快速、归并等)、查找、图论、动态规划等。 - 空间复杂度与时间复杂度分析:...
根据提供的文件信息,本文将详细解析读写锁`ReentrantReadWriteLock`以及`StampLock`在Java并发编程中的应用场景及其实现原理。 ### 一、读写锁介绍 #### 1.1 读写锁的基本概念 读写锁是一种特殊的锁机制,它可以...
- Java中的线程同步机制:synchronized、Lock(ReentrantLock、ReadWriteLock)、信号量Semaphore等,理解它们的原理和应用场景。 - 线程池的使用,如ExecutorService、ThreadPoolExecutor,如何合理设置核心线程...
Java后端面试题涵盖了许多核心领域,包括但不限于基础语法、面向对象编程、集合框架、多线程、JVM内存管理、网络编程、数据库操作、设计模式以及...深入理解和实践这些知识点将有助于提升你在Java后端领域的专业素养。
- **垂直拆分**:根据业务需求将应用和服务划分为多个独立的部分。 #### 设计模式与实践 - **单例模式**:对于频繁使用的大对象,采用单例模式减少对象创建和销毁的成本。 - **代理模式**:实现延迟加载、远程调用...
- MyBatis缓存:一级缓存、二级缓存的原理和配置。 - MyBatis Plus:提供更简洁的CRUD操作,简化开发。 6. **Redis**: - 数据类型:字符串、列表、集合、哈希、有序集合等。 - 主从复制:实现数据备份和高可用...
J.U.C的API结构设计非常精妙,它不仅提供了丰富的并发工具,还提供了一套流畅的API设计,使得开发者能够容易地将这些工具组合使用。例如,通过Executor框架,开发者可以非常容易地创建出各种不同类型的线程池,用于...
5. **队列设计**:引入消息队列(如RabbitMQ、Kafka),将大量请求异步化处理,缓解服务器压力,避免瞬间高并发对系统造成冲击。同时,消息队列能实现流量削峰填谷,保证系统稳定。 6. **分布式Id生成**:秒杀场景...
Java面试宝典主要涵盖了许多Java开发者在面试过程中可能会遇到的核心知识点。这包括但不限于语言基础、数据结构与算法、集合框架、多线程、异常处理、IO...通过系统地学习和实践这些知识,将有助于你在面试中脱颖而出。
阻塞队列广泛应用于各种并发场景中,如任务调度、消息传递、资源管理等。通过合理利用阻塞队列,可以有效提高系统的并发性能和资源利用率。 #### 二、传统阻塞队列的性能瓶颈 1. **同步机制的效率瓶颈**: - 传统...