锁定老帖子 主题:LRU缓存的实现
精华帖 (0) :: 良好帖 (3) :: 新手帖 (13) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2009-12-08
最后修改:2010-11-16
LinkedHashMap是一个现成的LRU实现。但其缺乏并发机制,并且没有实现满载删除条件(removeEldestEntry),因此本文认为LinkedHashMap是预留扩展的LRU类。 本文将继承该类进一步实现LRU缓存。 首先,并发使用可重入锁。对于封读锁后是否可写,封写锁后是否可读的问题,这是一个封锁策略。一般而言,读后,若写,会产生脏数据。写后,若读则没有问题。
可重入锁之读写锁,封锁顺序测试: package lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class LockTest1{ final static ReadWriteLock rwlock = new ReentrantReadWriteLock(); final static Lock readLock = rwlock.readLock(); final static Lock writeLock = rwlock.writeLock(); public static void main(String[] args) throws InterruptedException { System.out.println("test readlock-writelock"); lockR2W(); System.out.println("test writelock-readlock"); lockW2R(); System.out.println("test readlock-readlock"); lockRR(); System.out.println("test writelock-writelock"); lockWW(); } private static void lockWW() { writeLock.lock(); System.out.println("w lock."); try { if (writeLock.tryLock(1, TimeUnit.SECONDS)) System.out.println("w lock."); else System.out.println("when w lock, cannot w lock again."); } catch (InterruptedException e0) { e0.printStackTrace(); } try { writeLock.unlock(); System.out.println("w unlock."); writeLock.unlock(); System.out.println("w unlock."); } catch (Exception e) { //ignore; } } private static void lockRR() { readLock.lock(); System.out.println("r lock."); readLock.lock(); System.out.println("r lock."); readLock.unlock(); System.out.println("r unlock."); readLock.unlock(); System.out.println("r unlock."); } private static void lockW2R() throws InterruptedException { writeLock.lock(); System.out.println("w lock."); if (readLock.tryLock(1, TimeUnit.SECONDS)) System.out.println("r lock."); else System.out.println("when w lock, cannot r lock."); writeLock.unlock(); System.out.println("w unlock."); readLock.unlock(); System.out.println("r unlock."); } private static void lockR2W() { readLock.lock(); System.out.println("r lock."); try { if (writeLock.tryLock(1, TimeUnit.SECONDS)) System.out.println("w lock."); else System.out.println("when r lock, cannot w lock."); } catch (InterruptedException e0) { e0.printStackTrace(); } try { readLock.unlock(); System.out.println("r unlock."); writeLock.unlock(); System.out.println("w unlock."); } catch (Exception e) { //ignore; } } } 测试结果: test readlock-writelock r lock. when r lock, cannot w lock. r unlock. test writelock-readlock w lock. r lock. w unlock. r unlock. test readlock-readlock r lock. r lock. r unlock. r unlock. test writelock-writelock w lock. w lock. w unlock. w unlock.
LRU的意义在于最近被访问的数据总是可以最快从缓存中读取到。因此,下面要测试最新数据的位置和缓存已满时数据的处理。 package lru; import java.util.Iterator; import java.util.Map.Entry; public class LRUTest{ public static void main(String[] args) { int maxCapacity = 5; OjadbMap<Integer, String> lruMap = new OjadbMap<Integer, String>(maxCapacity); for (int i = 0; i < maxCapacity; i++) { lruMap.put(i, "data[" + i + "]"); } lruMap.get(3); Iterator<Entry<Integer, String>> iter = lruMap.entrySet().iterator(); while (iter.hasNext()) { System.out.println(iter.next().getValue()); } System.out.println(); lruMap.put(9, "data[ 9 ]"); Iterator<Entry<Integer, String>> iter1 = lruMap.entrySet().iterator(); while (iter1.hasNext()) { System.out.println(iter1.next().getValue()); } } } 测试结果: data[0] data[1] data[2] data[4] data[3] //最近访问的数据在队尾。 data[1] data[2] data[4] data[3] data[ 9 ] //最新访问的数据插入队尾(若队满,最老的数据被移除)。
本文使用的LRU缓存类: (参考:这个类是我的一个开源数据库项目中,缓存部分的实现类。 你可以通过访问http://code.google.com/p/ojadb 查看整个项目) /******************************************************************************************* * oJadb is free software: you can redistribute it and/or modify it under * the terms of the GNU General Public License as published by the Free Software Foundation, * either version 3 of the License, or (at your option) any later version. * * oJadb is distributed in the hope that it will be useful,but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along with this library. * If not, see <http://www.gnu.org/licenses/>. * * Author:EricHan * Time:2008-8-28 ******************************************************************************************/ package lru; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class OjadbMap<K, V> extends LinkedHashMap<K, V>{ private static final long serialVersionUID = -6970580442519842034L; private final int maxCapacity; private static final float DEFAULT_LOAD_FACTOR = 0.75f; //private final Lock lock = new ReentrantLock(); private final ReadWriteLock rwlock = new ReentrantReadWriteLock(); private final Lock readLock = rwlock.readLock(); private final Lock writeLock = rwlock.writeLock(); public OjadbMap(int maxCapacity) { super(maxCapacity, DEFAULT_LOAD_FACTOR, true); this.maxCapacity = maxCapacity; } @Override protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) { return size() > maxCapacity; } @Override public boolean containsKey(Object key) { try { readLock.lock(); return super.containsKey(key); } finally { readLock.unlock(); } } @Override public V get(Object key) { try { readLock.lock(); return super.get(key); } finally { readLock.unlock(); } } @Override public V put(K key, V value) { try { writeLock.lock(); return super.put(key, value); } finally { writeLock.unlock(); } } public int size() { try { readLock.lock(); return super.size(); } finally { readLock.unlock(); } } public void clear() { try { writeLock.lock(); super.clear(); } finally { writeLock.unlock(); } } public Collection<Map.Entry<K, V>> getAll() { try { readLock.lock(); return new ArrayList<Map.Entry<K, V>>(super.entrySet()); } finally { readLock.unlock(); } } } 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-12-08
最后修改:2009-12-08
麻烦楼主测一下,50条线程,每条10000次get,set操作时需要的耗时,我也写了一个支持并发的LRUMap,每秒可以支持30w次操作,机器是3G的CPU和2G的内存
|
|
返回顶楼 | |
发表时间:2009-12-08
最后修改:2009-12-08
我写了个测试,你可以在相同条件下跑跑。然后给出回复。
package lru; import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Performance{ static final int loop = 10000; static final int threadCount = 50; static OjadbMap<Integer, Integer> lruMap = new OjadbMap<Integer, Integer>(loop); static ExecutorService exec = Executors.newFixedThreadPool(threadCount); static long nn = 0; static int check=0; public static void main(String[] args) throws InterruptedException { Performance p = new Performance(); p.start(); } private void start() throws InterruptedException { loops(); Thread.sleep(7500); System.out.println(check+" spend time=" + nn); System.out.println(" every milli-seconds=" + (check / nn)); lruMap=null; exec.shutdown(); } private void loops() { for (int i = 0; i < threadCount; i++) { exec.execute(new Thread("thread-" + i){ @Override public void run() { final long begin = System.currentTimeMillis(); Random r = new Random(); for (int j = 0; j < loop; j++) { check++; int n = r.nextInt(100); if (null == lruMap.get(n)) { lruMap.put(n, j); } else { } } final long end = System.currentTimeMillis(); final long elapsed = end - begin; nn = nn + elapsed; } }); } } }[/color][/color] |
|
返回顶楼 | |
发表时间:2009-12-08
我用countdownloadlatch,同步用sync,250w/s,Lock readLock = rwlock.readLock(); 没有试
|
|
返回顶楼 | |
发表时间:2009-12-09
最后修改:2009-12-09
囧。原来hashlinklist真么神奇
他这个lru似乎是直接用hash function搞的? |
|
返回顶楼 | |
发表时间:2009-12-09
mikeandmore 写道 囧。原来hashlinklist真么神奇
super(maxCapacity, DEFAULT_LOAD_FACTOR, true); 注意第三个参数 |
|
返回顶楼 | |
发表时间:2009-12-09
火星来客 写道 mikeandmore 写道 囧。原来hashlinklist真么神奇
super(maxCapacity, DEFAULT_LOAD_FACTOR, true); 注意第三个参数 于是看代码去鸟。。。。 |
|
返回顶楼 | |
发表时间:2009-12-09
最后修改:2009-12-09
原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-
于是 我觉得lz的实现有点问题。。。 readlock和readlock互溶 于是会同时发生get,这估计也是lz想要的。 但是看看hashedlinklist的代码 第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。 |
|
返回顶楼 | |
发表时间:2009-12-09
哈哈
原来已经有前辈说过我的问题了。。。 http://www.iteye.com/topic/123856#377584 |
|
返回顶楼 | |
发表时间:2009-12-09
最后修改:2009-12-09
mikeandmore 写道 原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-
于是 我觉得lz的实现有点问题。。。 readlock和readlock互溶 于是会同时发生get,这估计也是lz想要的。 但是看看hashedlinklist的代码 第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。 这个也是我用synchronized的原因,get操作会修改链表,用ReentrantLock没问题,但是要用同一把锁做get和put,读写锁用在这里不合适 |
|
返回顶楼 | |