之前看redis的书,书上罗列源码的时候我总是在想,redis为什么不用Java实现!!
今天自己用Java写了一个简易的缓存,发现,redis不用Java实现可能是正确的:C语言可以自行回收内存,而Java不可以(我水平可能没达到,还没有自己回收过某个对象的内存),这样就导致了你的缓存中的对象有可能都过期了,你只是把这些过期对象的引用置空,但是什么时候回收这些内存,不是我们说了算的。
好,废话不多说了,上一段代码(代码如果写的不对、不合理的地方,还请大家一定指出)。
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class MyCache {
private Map<String, Value> map;
private int threshold; //达到百分之多少回收
private MemoryCalculateStrategy memoryCalculateStrategy;
public MyCache() {//todo: 改成单例模式
this(80, new SimpleMemoryCalculateStrategy());
}
public MyCache(int threshold) {
this(threshold, new SimpleMemoryCalculateStrategy());
}
public MyCache(int threshold, MemoryCalculateStrategy memoryCalculateStrategy) {
map = new ConcurrentHashMap<>();
this.threshold = threshold;
this.memoryCalculateStrategy = memoryCalculateStrategy;//todo: 改成工厂方法,会用户友好一些
}
class Value {
Object value;
long expireTime;
public Value(Object value, long expireTime) {
this.value = value;
this.expireTime = expireTime;
}
}
public boolean put(String k, Object v, Long timeToLive) {
checkMem();
long expireTime = System.currentTimeMillis() + timeToLive * 1000;
Value value = new Value(v, expireTime);
map.put(k, value);
return true;
}
private void checkMem() {
if (!memoryCalculateStrategy.calculate(threshold)) return;
synchronized (this){
if (memoryCalculateStrategy.calculate(threshold)) {
System.out.println("starting gc:内存占用百分比:"+(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory())*100/Runtime.getRuntime().totalMemory());
gc();
}
}
}
private void gc() {
for (String key : map.keySet()) {
long nowTime = System.currentTimeMillis();
if (map.get(key).expireTime < nowTime) {
map.remove(key);
}
}
System.gc();
System.out.println("end gc:内存占用百分比:"+(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory())*100/Runtime.getRuntime().totalMemory());
}
public Object get(String k) {
long nowTime = System.currentTimeMillis();
Value value = map.get(k);
if (value == null) return null;
if (value.expireTime < nowTime) {
map.remove(k); //lazy
}
return (map.get(k)).value;
}
public static void main(String[] args) {//test code
MyCache myCache = new MyCache();
for(int i=0;i<10;i++){
new Thread(() -> {
for (int i1 = 0; i1 < 10000000; i1++) {
List list = new ArrayList();
for (int j = 0; j < 100; j++) {
list.add(j);
}
myCache.put(i1 + "", list, 1L);
}
}).start();
}
}
}
public interface MemoryCalculateStrategy {
boolean calculate(int threshold);
}
public class SimpleMemoryCalculateStrategy implements MemoryCalculateStrategy {
@Override
public boolean calculate(int threshold) {
long usedMemorySize = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory());
long maxMemorySize = Runtime.getRuntime().totalMemory(); //最大可用内存
if (usedMemorySize * 100 / maxMemorySize > threshold) return true;
return false;
}
}
因缓存系统总会存在多个线程操作一个对象的情况,故用了线程安全的J.U.C.ConcurrentHashMap,因此正常情况下的put和set操作会在常数时间内完成。
调用构造方法的时候,可以自己限定内存用到百分之多少的时候,来遍历清除过期对象。
get时,如果值过期,则会将该对象从map中移除(但是并不是立即回收该对象的内存,下一次GC时才会回收)
设计时为了避免多个线程同时清理内存,因此此处要保证线程安全,但是这时一个缓存系统,效率不能太低,因此采用了double-check(参考了Spring的做法,Rod Johnson你听我解释........这不是抄袭,真的),这样可以既保证线程安全,效率也不会很低。
我代码中会有不合理的地方,比如,回收内存的时候直接System.gc(),这样会导致full gc,会stop the world,如果大家好的想法,还请大家指出来。
好了,虽说很多情况下不适合用Java编写,丝毫不撼动我爱Java的心~~
不知以后如果喜欢上别的语言,再看到这里会是什么心情。。。。。