欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java实现一个简单的缓存方法

程序员文章站 2024-02-28 16:23:16
缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率。下面我实现了一...

缓存是在web开发中经常用到的,将程序经常使用到或调用到的对象存在内存中,或者是耗时较长但又不具有实时性的查询数据放入内存中,在一定程度上可以提高性能和效率。下面我实现了一个简单的缓存,步骤如下。

创建缓存对象entitycache.java

public class entitycache {
  /**
   * 保存的数据
   */
  private object datas;

  /**
   * 设置数据失效时间,为0表示永不失效
   */
  private long timeout;

  /**
   * 最后刷新时间
   */
  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;
  }


}

定义缓存操作接口,icachemanager.java

public interface icachemanager {
  /**
   * 存入缓存
   * @param key
   * @param cache
   */
  void putcache(string key, entitycache cache);

  /**
   * 存入缓存
   * @param key
   * @param cache
   */
  void putcache(string key, object datas, long timeout);

  /**
   * 获取对应缓存
   * @param key
   * @return
   */
  entitycache getcachebykey(string key);

  /**
   * 获取对应缓存
   * @param key
   * @return
   */
  object getcachedatabykey(string key);

  /**
   * 获取所有缓存
   * @param key
   * @return
   */
  map<string, entitycache> getcacheall();

  /**
   * 判断是否在缓存中
   * @param key
   * @return
   */
  boolean iscontains(string key);

  /**
   * 清除所有缓存
   */
  void clearall();

  /**
   * 清除对应缓存
   * @param key
   */
  void clearbykey(string key);

  /**
   * 缓存是否超时失效
   * @param key
   * @return
   */
  boolean istimeout(string key);

  /**
   * 获取所有key
   * @return
   */
  set<string> getallkeys();
}

实现接口icachemanager,cachemanagerimpl.java

这里我使用了concurrenthashmap来保存缓存,本来以为这样就是线程安全的,其实不然,在后面的测试中会发现它并不是线程安全的。

public class cachemanagerimpl implements icachemanager {
  private static map<string, entitycache> caches = new concurrenthashmap<string, entitycache>();

  /**
   * 存入缓存
   * @param key
   * @param cache
   */
  public void putcache(string key, entitycache cache) {
    caches.put(key, 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()));
  }

  /**
   * 获取对应缓存
   * @param key
   * @return
   */
  public entitycache getcachebykey(string key) {
    if (this.iscontains(key)) {
      return caches.get(key);
    }
    return null;
  }

  /**
   * 获取对应缓存
   * @param key
   * @return
   */
  public object getcachedatabykey(string key) {
    if (this.iscontains(key)) {
      return caches.get(key).getdatas();
    }
    return null;
  }

  /**
   * 获取所有缓存
   * @param key
   * @return
   */
  public map<string, entitycache> getcacheall() {
    return caches;
  }

  /**
   * 判断是否在缓存中
   * @param key
   * @return
   */
  public boolean iscontains(string key) {
    return caches.containskey(key);
  }

  /**
   * 清除所有缓存
   */
  public void clearall() {
    caches.clear();
  }

  /**
   * 清除对应缓存
   * @param key
   */
  public void clearbykey(string key) {
    if (this.iscontains(key)) {
      caches.remove(key);
    }
  }

  /**
   * 缓存是否超时失效
   * @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;
  }

  /**
   * 获取所有key
   * @return
   */
  public set<string> getallkeys() {
    return caches.keyset();
  }
}

cachelistener.java,监听失效数据并移除。

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 + "缓存被清除");
           }
          } 
        }
      } 
    }.start();

  }
}

测试类testcache.java

public class testcache {
  logger logger = logger.getlogger("cachelog");
  /**
   * 测试缓存和缓存失效
   */
  @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
  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 {
              //因为+1和赋值操作不是原子性的,所以把它用synchronize块包起来
              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());
  }
}

testcachemanager()输出结果如下:

2017-4-17 10:33:51 io.github.brightloong.cache.testcache testcachemanager
信息: test:test
2017-4-17 10:33:51 io.github.brightloong.cache.testcache testcachemanager
信息: mytest:mytest
2017-4-17 10:34:01 io.github.brightloong.cache.cachelistener$1 run
信息: test缓存被清除
2017-4-17 10:34:06 io.github.brightloong.cache.cachelistener$1 run
信息: mytest缓存被清除
2017-4-17 10:34:11 io.github.brightloong.cache.testcache testcachemanager
信息: test:null
2017-4-17 10:34:11 io.github.brightloong.cache.testcache testcachemanager
信息: mytest:null

testthredsafe()输出结果如下(选出了各种结果中的一个举例):

2017-4-17 10:35:36 io.github.brightloong.cache.testcache testthredsafe
信息: 96

可以看到并不是预期的结果100,为什么呢?concurrenthashmap只能保证单次操作的原子性,但是当复合使用的时候,没办法保证复合操作的原子性,以下代码:

if (!cachemanagerimpl.iscontains(key)) {
              cachemanagerimpl.putcache(key, 1, 0);
            }

多线程的时候回重复更新value,设置为1,所以出现结果不是预期的100。所以办法就是在cachemanagerimpl.java中都加上synchronized,但是这样一来相当于操作都是串行,使用concurrenthashmap也没有什么意义,不过只是简单的缓存还是可以的。或者对测试方法中的run里面加上synchronized块也行,都是大同小异。更高效的方法我暂时也想不出来,希望大家能多多指教。