Java實現簡易的快取
阿新 • • 發佈:2018-11-05
之前看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的心~~
不知以後如果喜歡上別的語言,再看到這裡會是什麼心情。。。。。