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.