Java实现简易的缓存

之前看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的心~~

不知以后如果喜欢上别的语言,再看到这里会是什么心情。。。。。

猜你喜欢

转载自blog.csdn.net/qq_37043780/article/details/82284021