Java implements a simple caching method

  • 2020-06-23 00:31:53
  • OfStack

Caching is often used in web development. The objects that are often used or called by the program are stored in memory, or the query data that takes a long time but does not have real time is put into memory. To a certain extent, performance and efficiency can be improved. I implemented a simple cache as follows.

Create the cache object EntityCache.java


public class EntityCache {
  /**
   *  Saved data 
   */
  private Object datas;

  /**
   *  Set the data expiration time , for 0 Means never to fail 
   */
  private long timeOut;

  /**
   *  Last refresh time 
   */
  private long lastRefeshTime;

  public EntityCache(Object datas, long timeOut, long lastRefeshTime) {
    this.datas = datas;
    this.timeOut = timeOut;
    this.lastRefeshTime = lastRefeshTime;
  }
  public Object getDatas() {
    return datas;
  }
  public void setDatas(Object datas) {
    this.datas = datas;
  }
  public long getTimeOut() {
    return timeOut;
  }
  public void setTimeOut(long timeOut) {
    this.timeOut = timeOut;
  }
  public long getLastRefeshTime() {
    return lastRefeshTime;
  }
  public void setLastRefeshTime(long lastRefeshTime) {
    this.lastRefeshTime = lastRefeshTime;
  }


}

Define the cache operation interface, ES9en.java


public interface ICacheManager {
  /**
   *  Deposited in the cache 
   * @param key
   * @param cache
   */
  void putCache(String key, EntityCache cache);

  /**
   *  Deposited in the cache 
   * @param key
   * @param cache
   */
  void putCache(String key, Object datas, long timeOut);

  /**
   *  Get the corresponding cache 
   * @param key
   * @return
   */
  EntityCache getCacheByKey(String key);

  /**
   *  Get the corresponding cache 
   * @param key
   * @return
   */
  Object getCacheDataByKey(String key);

  /**
   *  Get all caches 
   * @param key
   * @return
   */
  Map<String, EntityCache> getCacheAll();

  /**
   *  Determine if it is in the cache 
   * @param key
   * @return
   */
  boolean isContains(String key);

  /**
   *  Clear all caches 
   */
  void clearAll();

  /**
   *  Clear the corresponding cache 
   * @param key
   */
  void clearByKey(String key);

  /**
   *  Cache timeout invalidated 
   * @param key
   * @return
   */
  boolean isTimeOut(String key);

  /**
   *  Get all the key
   * @return
   */
  Set<String> getAllKeys();
}

Implement interface ICacheManager, ES15en.java

I used ConcurrentHashMap to save the cache because I thought it was thread-safe, but it wasn't, as we'll see later in the test.


public class CacheManagerImpl implements ICacheManager {
  private static Map<String, EntityCache> caches = new ConcurrentHashMap<String, EntityCache>();

  /**
   *  Deposited in the cache 
   * @param key
   * @param cache
   */
  public void putCache(String key, EntityCache cache) {
    caches.put(key, cache);
  }

  /**
   *  Deposited in the cache 
   * @param key
   * @param cache
   */
  public void putCache(String key, Object datas, long timeOut) {
    timeOut = timeOut > 0 ? timeOut : 0L;
    putCache(key, new EntityCache(datas, timeOut, System.currentTimeMillis()));
  }

  /**
   *  Get the corresponding cache 
   * @param key
   * @return
   */
  public EntityCache getCacheByKey(String key) {
    if (this.isContains(key)) {
      return caches.get(key);
    }
    return null;
  }

  /**
   *  Get the corresponding cache 
   * @param key
   * @return
   */
  public Object getCacheDataByKey(String key) {
    if (this.isContains(key)) {
      return caches.get(key).getDatas();
    }
    return null;
  }

  /**
   *  Get all caches 
   * @param key
   * @return
   */
  public Map<String, EntityCache> getCacheAll() {
    return caches;
  }

  /**
   *  Determine if it is in the cache 
   * @param key
   * @return
   */
  public boolean isContains(String key) {
    return caches.containsKey(key);
  }

  /**
   *  Clear all caches 
   */
  public void clearAll() {
    caches.clear();
  }

  /**
   *  Clear the corresponding cache 
   * @param key
   */
  public void clearByKey(String key) {
    if (this.isContains(key)) {
      caches.remove(key);
    }
  }

  /**
   *  Cache timeout invalidated 
   * @param key
   * @return
   */
  public boolean isTimeOut(String key) {
    if (!caches.containsKey(key)) {
      return true;
    }
    EntityCache cache = caches.get(key);
    long timeOut = cache.getTimeOut();
    long lastRefreshTime = cache.getLastRefeshTime();
    if (timeOut == 0 || System.currentTimeMillis() - lastRefreshTime >= timeOut) {
      return true;
    }
    return false;
  }

  /**
   *  Get all the key
   * @return
   */
  public Set<String> getAllKeys() {
    return caches.keySet();
  }
}

CacheListener.java, listen for invalid data and remove.


public class CacheListener{
  Logger logger = Logger.getLogger("cacheLog");
  private CacheManagerImpl cacheManagerImpl;
  public CacheListener(CacheManagerImpl cacheManagerImpl) {
    this.cacheManagerImpl = cacheManagerImpl;
  }

  public void startListen() {
    new Thread(){
      public void run() {
        while (true) {
          for(String key : cacheManagerImpl.getAllKeys()) {
            if (cacheManagerImpl.isTimeOut(key)) {
             cacheManagerImpl.clearByKey(key);
             logger.info(key + " Cache cleared ");
           }
          } 
        }
      } 
    }.start();

  }
}

The test class TestCache java


public class TestCache {
  Logger logger = Logger.getLogger("cacheLog");
  /**
   *  Test cache and cache invalidation 
   */
  @Test
  public void testCacheManager() {
    CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
    cacheManagerImpl.putCache("test", "test", 10 * 1000L);
    cacheManagerImpl.putCache("myTest", "myTest", 15 * 1000L);
    CacheListener cacheListener = new CacheListener(cacheManagerImpl);
    cacheListener.startListen();
    logger.info("test:" + cacheManagerImpl.getCacheByKey("test").getDatas());
    logger.info("myTest:" + cacheManagerImpl.getCacheByKey("myTest").getDatas());
    try {
      TimeUnit.SECONDS.sleep(20);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    logger.info("test:" + cacheManagerImpl.getCacheByKey("test"));
    logger.info("myTest:" + cacheManagerImpl.getCacheByKey("myTest"));
  }

  /**
   *  Test thread safety 
   */
  @Test
  public void testThredSafe() {
    final String key = "thread";
    final CacheManagerImpl cacheManagerImpl = new CacheManagerImpl();
    ExecutorService exec = Executors.newCachedThreadPool();
    for (int i = 0; i < 100; i++) {
      exec.execute(new Runnable() {
        public void run() {
            if (!cacheManagerImpl.isContains(key)) {
              cacheManagerImpl.putCache(key, 1, 0);
            } else {
              // because +1 The and assignment operations are not atomic, so use them synchronize Block wrapped up 
              synchronized (cacheManagerImpl) {
                int value = (Integer) cacheManagerImpl.getCacheDataByKey(key) + 1; 
                cacheManagerImpl.putCache(key,value , 0);
              }
            }
        }
      });
    }
    exec.shutdown(); 
    try {
      exec.awaitTermination(1, TimeUnit.DAYS);
    } catch (InterruptedException e1) {
      e1.printStackTrace();
    } 

    logger.info(cacheManagerImpl.getCacheDataByKey(key).toString());
  }
}

The output of testCacheManager() is as follows:


2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
 information : test:test
2017-4-17 10:33:51 io.github.brightloong.cache.TestCache testCacheManager
 information : myTest:myTest
2017-4-17 10:34:01 io.github.brightloong.cache.CacheListener$1 run
 information : test Cache cleared 
2017-4-17 10:34:06 io.github.brightloong.cache.CacheListener$1 run
 information : myTest Cache cleared 
2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
 information : test:null
2017-4-17 10:34:11 io.github.brightloong.cache.TestCache testCacheManager
 information : myTest:null

The output of testThredSafe() is as follows (an example of the various results is selected) :


2017-4-17 10:35:36 io.github.brightloong.cache.TestCache testThredSafe
 information : 96

You can see it's not the expected result 100. Why is that? ConcurrentHashMap can only guarantee the atomicity of a single operation, but there is no way to guarantee the atomicity of a compound operation when it is used. The following code:


if (!cacheManagerImpl.isContains(key)) {
              cacheManagerImpl.putCache(key, 1, 0);
            }

Update value repeatedly when multithreading, set to 1, so the result is not expected 100. So the solution is to add synchronized to CacheManagerImpl.java, but this 1 is equivalent to the serial operation, it doesn't make sense to use ConcurrentHashMap, but just simple caching is ok. Or if you add blocks of synchronized to the run, it's pretty much the same thing. I can't think of a more efficient way, but I hope you can give me more advice.


Related articles: