2018年9月12日 星期三

EasyCache:輕量化、簡單使用的 記憶體暫存元件

EasyCache
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.gorilla.util.DateUtil;
import com.gorilla.util.cache.exception.LoadingCacheDataException;

public class EasyCache<K, V> implements Cache<K, V> {
 // private Logger logger = Logger.getLogger(this.getClass());
 private Logger logger = LoggerFactory.getLogger(this.getClass());
 private ConcurrentHashMap<K, ValueHolder<V>> cacheMap = null;
 private CacheLoader<K, V> loader;
 // the default time(2 hours) which data to live for the duration.
 private Long duration = 3600 * 1 * 1000L; 
 private Object loadingKey = new Object();
 //private Object removeKey  = new Object();
 private CacheCleaner cacheCleaner;
 private ScheduledThreadPoolExecutor scheduleExecutor = (ScheduledThreadPoolExecutor) Executors
   .newScheduledThreadPool(1);
 private final Long SCHEDULE_SLEEP_TIME_MIILLISECOND = 1 * 60 * 1000L;

 EasyCache(CacheLoader<K, V> loader, Long duration) {
  if (cacheMap == null)
   cacheMap = new ConcurrentHashMap<K, ValueHolder<V>>();

  if (duration != null && duration > 0)
   this.duration = duration;

  this.loader = loader;

  scheduleExecutor.schedule(new Runnable() {

   @Override
   public void run() {
    // TODO Auto-generated method stub
    while (true) {
     if (cacheMap.size() > 0) {
      if (cacheCleaner == null) {
       cacheCleaner = new ExpiredDurationCacheCleaner(duration);
      }
      
      Map<K, ValueHolder<V>> copyOfMap = new HashMap<K, ValueHolder<V>>(cacheMap);
      Set<Entry<K, ValueHolder<V>>> copySets = copyOfMap.entrySet();
      Iterator<Entry<K, ValueHolder<V>>> copyItors = copySets.iterator();
      List<K> removeKeys = new ArrayList<K>();
      Entry<K, ValueHolder<V>> entry;
      while (copyItors.hasNext()) {
       entry = copyItors.next();
       if (cacheCleaner.cleanExpiredValue(entry.getValue().lastAccessTimeSecond))
        removeKeys.add(entry.getKey());
      }
      if (removeKeys.size() > 0) {
       synchronized (loadingKey){
        for (K key : removeKeys) {
         cacheMap.remove(key);
         logger.info(String.format("Remove the Key:%s's data at %s", key,
           DateUtil.getInstance().getYYYYMMDDWithTimeFormat().format(new Date())));
        }
       }
       
      }
     }

     try {
      Thread.sleep(SCHEDULE_SLEEP_TIME_MIILLISECOND);
     } catch (InterruptedException e) {
      // TODO Auto-generated catch block
      logger.error("thread sleep exception, e:" + e);
     }
    }

   }

  }, 1000, TimeUnit.MILLISECONDS);
 }

 @Override
 public V get(K key) throws NullPointerException, LoadingCacheDataException {
  // TODO Auto-generated method stub
  if (cacheMap.get(key) == null) {
   synchronized (loadingKey) {
    if (cacheMap.get(key) == null) {
     try {
      V value = loader.load(key);
      ValueHolder<V> holder = new ValueHolder<V>(value);
      cacheMap.put(key, holder);
      logger.info(String.format("The Key:%s's  channelCategoryViews is loading to cache", key.toString()));
      return value;
     } catch (LoadingCacheDataException e) {
      logger.error("get key:" + key.toString() + "'s data exception:" + e);
      throw new LoadingCacheDataException(e.getMessage(), e);
     } catch (NullPointerException e) {
      logger.error("put data into cache Map exception", e);
      throw new NullPointerException(e.getMessage());
     }

    } else {
     ValueHolder<V> holder = cacheMap.get(key);
     //holder.lastAccessTimeSecond = System.currentTimeMillis();
     logger.debug(String.format("The Key:%s's new expired date is %s", key.toString(),
       this.newExpiredDate(holder.lastAccessTimeSecond)));
     return cacheMap.get(key).value;
    }
   }
  } else {
   ValueHolder<V> holder = cacheMap.get(key);
   //holder.lastAccessTimeSecond = System.currentTimeMillis();
   return holder.value;
  }
 }

 @Override
 public Boolean removeAll() {
  // TODO Auto-generated method stub
  Boolean result = true;
  try {
   if (cacheMap.size() > 0) {
    synchronized (loadingKey){
     cacheMap.clear();
    }
   }
  } catch (Exception e) {
   logger.error("clear cache error.", e);
   result = false;
  }
  return result;
 }

 private Date newExpiredDate(Long lastAccessTimeSecond) {
  Date date = new Date(this.duration + lastAccessTimeSecond);
  DateUtil.getInstance().getYYYYMMDDWithTimeFormat().format(date);
  return date;
 }

 private class ExpiredDurationCacheCleaner extends CacheCleaner {

  public ExpiredDurationCacheCleaner(Long duration) {
   super(duration);
   // TODO Auto-generated constructor stub
  }

  /*
   * param :time: the last access time result : return true if the data
   * has expired and has to be remove from cache.
   */
  @Override
  public boolean cleanExpiredValue(long time) {
   // TODO Auto-generated method stub
   boolean result = false;
   if ((System.currentTimeMillis() - time) > this.duration)
    result = true; // clean the cache data.
   else
    result = false;

   return result;
  }

 }

 protected class ValueHolder<V> {

  public Long lastAccessTimeSecond;
  public V value;

  public ValueHolder(V value) {
   this.value = value;
   this.lastAccessTimeSecond = System.currentTimeMillis();
  }

 }

 public static void main(String[] args) {
  Long newExpired = 60000L + System.currentTimeMillis();
  Date d = new Date(newExpired);
  Integer a = 100;
  String test = String.format("The Key:%s's new expired date is %s", a.toString(),
    DateUtil.getInstance().getYYYYMMDDWithTimeFormat().format(d));

  out.println(test);
  Map<String, Integer> aMap = new HashMap<String, Integer>();

 }

}

沒有留言:

張貼留言