前言
实现LRU算法,注意观察者模式、并发(读写锁、线程池)的运用
核心类:LRUMap
成员变量
LinkedHashMap<K,V> map —— 底层存放元素( key-value )的容器。
ConcurrentLinkedQueue<LRUMapEvictionListener<K,V>> listeners —— 监听器队列,存放注册到该LRUMap的所有监听器(通过 LRUMap的 addListener注册 ),具体监听器实现 LRUMapEvictionListener接口,实现void objectEvicted(K key, V value)方法(对象逐出处理程序,详见 )。当执行LRUMap的evict、setCapacity、put操作时,会notify listener,并由线程池中的线程来遍历 listeners 执行各个监听器的处理程序(即 objectEvicted )。
ExpirationProcessor exProcess —— 内部类,线程类(继承Thread,是个守护线程setDaemon(true)),唤作“阎王线程”。持有成员属性Map<Object,Long> expirations唤作“死亡手册”(放置了key和lastAccess最后访问时间)和long expireTime(构造阎王线程实例时候设置的存活时间)。 “阎王线程”会不断地定时地检查死亡名单,如果名单中某个元素达到死亡时间(当前时间 - lastAccess > expireTime),则将该元素从名单 及 容器 中删除并notify listener做相应处理操作。
“阎王线程”何时新建、start呢?
在实例化LRUMap的时候,会根据设置的存活时间 expireTime决定是否启动“阎王线程”,如果实例化LRUMap时不指定 expireTime 或者设置为0,则不会有人定期检查容器中的元素是否到了死亡时间了。
if(expired > 0){
exProcess = new ExpirationProcessor(this,expired*1000L);
exProcess.start();
}
ThreadPoolExecutor poolExecutor ——线程池用于异步执行监听器处理程序。在调用LRUMap构造的时候实例化。
poolExecutor = new ThreadPoolExecutor(1,10,120L,TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactoryImpl(null,"LRUMap Listener Thread")
);
poolExecutor.execute(new Runnable() {
public void run() {
doNotifyListeners(rmKey,rmVal);
}
});
ReadWriteLock readWriteLock = new ReentrantReadWriteLock() ——通过readLock()和writeLock()可以获得读锁和写锁。
内部类
private static class ExpirationProcessor extends Thread —— “阎王线程”类,在run()方法里while(true)体,会不断地定期地(每隔10s)检查死亡名单,将达到死亡时间的成员“干掉”。
将key及对应的最近访问时间放到阎王线程持有的死亡名单expirations里
public Long put(Object key, Long value) {
return expirations.put(key, value);
}
构造方法
public LRUMap(int size) —— expired存活时间默认为0
public LRUMap(int size,int expired)
public LRUMap(int size,int expired) {
if(size <= 0){
throw new IllegalArgumentException("Size should larger than 0");
}
map = new LinkedHashMap<K,V>(size);
this.capacity = size;
listeners = new ConcurrentLinkedQueue<LRUMapEvictionListener<K,V>>();
poolExecutor = new ThreadPoolExecutor(1,10,120L,TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(),
new ThreadFactoryImpl(null,"LRUMap Listener Thread")
);
if(expired > 0){
exProcess = new ExpirationProcessor(this,expired*1000L);
exProcess.start();
}
}
成员方法
public boolean containsKey(K key) ——判断是否包含key,注意读锁
public boolean containsKey(K key){
Lock lock = readWriteLock.readLock();
lock.lock();
try{
return map.containsKey(key);
}finally{
lock.unlock();
}
}
public V get(K key) ——从容器里获得key对应的value。根据LRU算法,如果根据key获取元素时,该元素会被认为最近使用,执行先删后加(加在链表尾部表示活跃使用),然后返回key对应的value,注意读写锁
public V get(K key){
Lock lock = readWriteLock.readLock();
lock.lock();
try {
if(map.containsKey(key)){
Lock writeLock = readWriteLock.writeLock();
lock.unlock();
writeLock.lock();
try {
//体现LRU算法:最近使用的元素会被放在最后面位置
map.put(key,map.remove(key)); // Most-Recent used is always in last
if(exProcess != null){
exProcess.put(key,System.currentTimeMillis());
}
}finally {
lock.lock();
writeLock.unlock();
}
return map.get(key);
}
return null;
} finally {
lock.unlock();
}
}
public V peek(K key) ——返回key对应的value。peek与get的区别
public V peek(K key){
Lock lock = readWriteLock.readLock();
lock.lock();
try {
return map.get(key);
} finally {
lock.unlock();
}
}
public void put(K key, V value) ——如果key已经存在,则先删后加(加在链表尾部),如果容器已满,则先删除第一个元素再add。
public void put(K key, V value){
if(key == null){
throw new IllegalArgumentException("Key object cannot be null.");
}
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
if(map.containsKey(key)){
map.remove(key);
}else{
if(capacity == 0){
return;
}
//体现LRU算法:如果容器满了,则将最老的元素逐出,
//在这里最老的元素放置在LinkHashMap第一个位置上
if(map.size()==capacity){ // reach the limit of key list
K rmKey = map.keySet().iterator().next(); // remove the eldest(LRU) one
V rmVal = map.remove(rmKey);
if(exProcess != null){
exProcess.remove(rmKey);
}
notifyListeners(rmKey,rmVal);
}
if(exProcess != null){
exProcess.put(key,System.currentTimeMillis());
}
}
map.put(key,value);
} finally {
lock.unlock();
}
}
public int size() ——容器当前大小
public int size(){
Lock lock = readWriteLock.readLock();
lock.lock();
try{
return map.size();
}finally{
lock.unlock();
}
}
public int getCapacity() ——获得容器容量
public void setCapacity(int newSize) ——设置容器容量,如果新设置的容量大小(newSize)小于容器已经包含的元素数(oldSize),需要将部分元素丢弃掉,从 LinkedHashMap头开始 删除(oldSize-newSize)个元素。
public void setCapacity(int newSize){
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
this.capacity = newSize;
if(map.size() > capacity){
int count = map.size()-capacity;
//体现LRU算法
for(Iterator<K> itr = map.keySet().iterator();itr.hasNext()&&(count > 0);count--){ // shrink the map
K rmKey = itr.next(); // remove the eldest(LRU) one
V rmVal = map.get(rmKey);
itr.remove();
if(exProcess != null){
exProcess.remove(rmKey);
}
notifyListeners(rmKey,rmVal);
}
}
} finally {
lock.unlock();
}
}
public V remove(K key) ——将元素从LRUMap容器中及“阎王线程”持有的死亡名单中删除。
public V remove(K key) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
if(exProcess != null){
exProcess.remove(key);
}
return map.remove(key);
}finally{
lock.unlock();
}
}
public V evict(K key) ——与remove差别,多了notifyListeners(key,val)
public V evict(K key) {
Lock lock = readWriteLock.writeLock();
lock.lock();
try {
if(exProcess != null){
exProcess.remove(key);
}
V val = map.remove(key);
if(val != null){
notifyListeners(key,val);
}
return val;
}finally{
lock.unlock();
}
}
public List<K> keys() ——获得key的列表
public List<K> keys(){
Lock lock = readWriteLock.readLock();
lock.lock();
try{
return new LinkedList<K>(map.keySet());
}finally{
lock.unlock();
}
}
public List<K> getMRUKeys(int num) ——获得排在后面num个元素的key。后面num个元素一般是比较活跃的元素。
Listener相关方法:
(注:运用了观察者模式,模式详见参考1,当LRUMap的状态发生变化时,比如调用了evict、setCapacity、put方法时,通知观察者(监听器Listener)做相应处理,具体监听器会实现 LRUMapEvictionListener接口 )
public void addListener(LRUMapEvictionListener<K,V> listener) ——将Listener实例加到listeners queue里
public boolean removeListener(LRUMapEvictionListener<K,V> listener) ——从listeners queue里删除该Listener实例
private void notifyListeners(final K rmKey,final V rmVal) ——在evict、setCapacity、put里调用
private void notifyListeners(final K rmKey,final V rmVal) {
if(listeners.isEmpty()){
return;
}
poolExecutor.execute(new Runnable() {
public void run() {
doNotifyListeners(rmKey,rmVal);
}
});
}
private void doNotifyListeners(final K rmKey,final V rmVal)
private void doNotifyListeners(final K rmKey,final V rmVal) {
if(listeners.isEmpty()){
return;
}
for (LRUMapEvictionListener<K,V> l : listeners) {
l.objectEvicted(rmKey,rmVal);
}
}
监听器接口:
public interface LRUMapEvictionListener<K, V> {
void objectEvicted(K key, V val);
}
构造LRUMap后,通过 addListener注册监听器实例(可以多次注册,监听器实例存放在LRUMap的成员属性 listeners中 )。
void objectEvicted(K key, V val)传入key和value,从方法名看叫做“对象逐出方法”,一般是在调用了LRUMap的一些操作如 evict、setCapacity、put(按照LRU算法这些操作通常涉及到元素的添加和删除以及位置的变化)时,会调用该处理程序,该处理程序针对不同应用场景有不同实现。
如下示例
private LRUMap<String, IndexWriter> writerCache = new LRUMap<String, IndexWriter>(writerCacheSize, expireTime * 60);
//注册监听器
writerCache.addListener(new LRUMapEvictionListener<String, IndexWriter>() {
//监听器处理程序
@Override
public void objectEvicted(String name, IndexWriter writer) {
if (log.isInfoEnabled()) {
log.info("IndexWriter of :" + name + " is evicted,going to evict correspondent IndexReader .");
}
readerCache.evict(name);
numberOfWriterEvicted.incrementAndGet();
try {
writer.close();
}catch (Exception e) {
log.warn("failed to close writer of :"+name, e);
}
}
});
线程工厂:
后记
1.体会读写锁的使用
http://nemogu.iteye.com/blog/1409879
2.java.util和java.util.concurrent中集合类
3.实现LRUMap时为什么使用观察者模式
LinkedHashMap<K,V> map中的元素被逐出(Evicte)后,如果想对这个被逐出的对象做处理(比如close)或者做一些其他相关操作,使用观察者模式,可以在map中的元素被逐出时,调用观察者类(监听器类)。
参考
1.观察者模式 http://nemogu.iteye.com/admin/blogs/1407857