论坛首页 综合技术论坛

LRU缓存的实现

浏览 15054 次
精华帖 (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();
		}
	}
}
 
   发表时间:2009-12-08   最后修改:2009-12-08
麻烦楼主测一下,50条线程,每条10000次get,set操作时需要的耗时,我也写了一个支持并发的LRUMap,每秒可以支持30w次操作,机器是3G的CPU和2G的内存
0 请登录后投票
   发表时间: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]
0 请登录后投票
   发表时间:2009-12-08  
我用countdownloadlatch,同步用sync,250w/s,Lock readLock = rwlock.readLock();  没有试
0 请登录后投票
   发表时间:2009-12-09   最后修改:2009-12-09
囧。原来hashlinklist真么神奇

他这个lru似乎是直接用hash function搞的?
0 请登录后投票
   发表时间:2009-12-09  
mikeandmore 写道
囧。原来hashlinklist真么神奇

super(maxCapacity, DEFAULT_LOAD_FACTOR, true); 
注意第三个参数
0 请登录后投票
   发表时间:2009-12-09  
火星来客 写道
mikeandmore 写道
囧。原来hashlinklist真么神奇

super(maxCapacity, DEFAULT_LOAD_FACTOR, true); 
注意第三个参数

于是看代码去鸟。。。。
0 请登录后投票
   发表时间:2009-12-09   最后修改:2009-12-09
原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-

于是
我觉得lz的实现有点问题。。。
readlock和readlock互溶

于是会同时发生get,这估计也是lz想要的。
但是看看hashedlinklist的代码
第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。
0 请登录后投票
   发表时间:2009-12-09  
哈哈
原来已经有前辈说过我的问题了。。。

http://www.iteye.com/topic/123856#377584
0 请登录后投票
   发表时间:2009-12-09   最后修改:2009-12-09
mikeandmore 写道
原来也是搞链表鸟。。。嗯。。。还是自己写靠谱。。。-,-

于是
我觉得lz的实现有点问题。。。
readlock和readlock互溶

于是会同时发生get,这估计也是lz想要的。
但是看看hashedlinklist的代码
第323行,会开始对lru queue进行访问,要把当前的访问放在queue head。这会发生争抢。造成链表出现一些诡异的现象。

这个也是我用synchronized的原因,get操作会修改链表,用ReentrantLock没问题,但是要用同一把锁做get和put,读写锁用在这里不合适
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics