• 周六. 10 月 5th, 2024

5G编程聚合网

5G时代下一个聚合的编程学习网

热门标签

Write a simple cache operation class in Java

King Wang

1 月 3, 2022

Preface

The use of caching has become a commonplace in development , Commonly used tools that deal with caching, such as Redis、MemCache etc. , But sometimes it may require some simple caching , There’s no need to use this special caching tool , Then write a cache class is the most appropriate .

One 、 analysis

First, analyze how to design cache class , Here I implement a cache class in a very simple way , This is the design I’ve been using all along .

In order to clarify the function , An interface first defines a class CacheInt, Then there is the tool class for caching implementation CacheUtil. And then look at the functions , For easy access , The cache should be accessed in the form of key value pairs , In order to adapt to more scenes , So you can add a cache expiration time when accessing , Then add other common additions 、 obtain 、 Delete 、 Cache size 、 Whether there is key、 Clean up the expired cache and so on , This is almost the way the cache tool works .

Cache class need to pay attention to the problem :

  1. Cache objects should be unique , That is, the singleton ;
  2. The operation method of cache should be synchronized , To prevent errors in multithreading concurrency ;
  3. The cache container should have high concurrency performance ,ConcurrentHashMap It’s a good choice .

Two 、 Concrete realization

1. CacheInt Definition of interface

CacheInt The interface is defined as follows :

public interface CacheInt {

/**
* Deposited in the cache , This process always clears the expired cache
* @param key key
* @param value value
* @param expire Expiration time , Unit second , If it is less than 1 It means long-term preservation
*/
void put(String key, Object value, int expire);
/**
* Access to the cache ,1/3 The probability of clearing the expired cache
* @param key key
* @return Object object
*/
Object get(String key);
/**
* Get cache size
* @return int
*/
int size();
/**
* Is there a cache
* @param key key
* @return boolean
*/
boolean isContains(String key);
/**
* Get all cached keys
* @return Set aggregate
*/
Set<String> getAllKeys();
/**
* Delete a cache
* @param key key
*/
void remove(String key);
}

2. CacheUtil The concrete realization of

The core of cache implementation is CacheUtil, The following is illustrated with the notes , In order to avoid the redundancy of the article , The following screenshot is the complete source screenshot , And keep it in order .

The first is the definition of a class and its properties , The instance object of this class uses volatile Embellish to improve visibility , Initialization cache capacity is used to initialize ConcurrentHashMap The size of the cache container , This size is optimized according to the actual application scenario .

import java.util.Iterator;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author beifengtz
* <a href='http://www.beifengtz.com'>www.beifengtz.com</a>
* Created in 13:24 2019/7/13
*/
public class CacheUtil implements CacheInt{

// The instance object of this class , Single case 
private static volatile CacheUtil instance;
// Initialize cache capacity 
private static final int CACHE_INITIAL_SIZE = 8;
// Cache container definition 
private static ConcurrentHashMap<String, Entry> cacheMap;
// Then there's the inner class Entry The definition of , This class is used to store actual data , In order to facilitate the processing of expiration time , Add initialization timestamp 、 Expiration time and other attributes .
// The definition of the structure of stored content 
static class Entry {

long initTime;// Storage time 
int expire; // Company : second 
Object data;// Specific data 
Entry(long initTime, int expire, Object data) {

this.initTime = initTime;
this.expire = expire;
this.data = data;
}
}
// Then we use the double lock singleton to obtain the instance object of this class , Because singleton can only have unique characteristics , So notice that the constructor needs to be set to private
private CacheUtil() {

cacheMap = new ConcurrentHashMap<>(CACHE_INITIAL_SIZE);
}
public static CacheUtil getInstance() {

if (instance == null) {

synchronized (CacheUtil.class) {

if (instance == null) {

instance = new CacheUtil();
}
return instance;
}
}
return instance;
}
// The next step is to store cache data `put()` Method ,
// there `clearExpiredCache()` Is to clean up the expired cache ,
// You'll see the method body later , Because there are fewer caches in my project ,
// So here I fixed the expiration time cache to be cleaned before each save ,
// Here you can optimize according to the actual situation of your project .
public synchronized void put(String key, Object value, int expire) {

clearExpiredCache();
Entry entry = new Entry(System.currentTimeMillis(), expire, value);
cacheMap.put(key, entry);
}
// And then it's getting the cache `get()` Method , Because it takes a lot of time to get data ,
// So here I set a one-third probability to clean up the expired cache , Release heap memory appropriately ,
// At the same time, it detects whether it is expired , If it's expired and you get it , Delete and return empty .
public synchronized Object get(String key) {

// Construct a one-third chance to clear the expired cache 
if(new Random().nextInt(12) > 8){

clearExpiredCache();
}
if (cacheMap.containsKey(key)) {

Entry entry = cacheMap.get(key);
if (entry.expire > 0 && System.currentTimeMillis() > entry.expire * 1000 + entry.initTime) {

cacheMap.remove(key);
return null;
} else {

return entry.data;
}
} else {

return null;
}
}
// Then there are some more conventional methods , See the code for details 
public int size() {

return cacheMap.size();
}
@Override
public boolean isContains(String key) {

return cacheMap.containsKey(key);
}
@Override
public Set<String> getAllKeys() {

return cacheMap.keySet();
}
@Override
public void remove(String key) {

cacheMap.remove(key);
}
// The last way is to clean up the expired cache , Here you can choose to start a monitor 
// Threads clean up the cache in real time , You can also choose to do a clean-up at the right time ,
// For example, I exist here put and get Fixed or probabilistic cleaning of the cache during operation .
private synchronized void clearExpiredCache() {

Iterator<Map.Entry<String, Entry>> iterator = cacheMap.entrySet().iterator();
while (iterator.hasNext()) {

Map.Entry<String, Entry> entry = iterator.next();
if (entry.getValue().expire > 0 &&
System.currentTimeMillis() > entry.getValue().expire * 1000 + entry.getValue().initTime) {

iterator.remove();
}
}
}
}

3、 … and 、 Concurrent test

Common implementation tests are not shown here , There must be no problem , Readers can simply write some test samples , This is mainly about concurrent testing , Because in the actual situation, there are concurrent processing cache conditions , To make sure it’s right , So concurrent testing is a must , Here’s a sample of my test .

@Test
public void concurrentCacheTest() {

final int LOOP_TIMES = 1000;// cycles 
// Thread pool , start-up 10 Child thread to process 
ExecutorService es = Executors.newFixedThreadPool(10);
// Store randomly generated key
ConcurrentLinkedQueue<String> clq = new ConcurrentLinkedQueue<>();
// Define two counters , Used to measure two concurrent processes 
CountDownLatch count1 = new CountDownLatch(LOOP_TIMES);
CountDownLatch count2 = new CountDownLatch(LOOP_TIMES);
// The count of cache operations 
AtomicInteger atomicInteger = new AtomicInteger(0);
// Test concurrency put performance 
for (int i = 0; i < LOOP_TIMES; i++) {

es.execute(new Runnable() {

@Override
public void run() {

String key = String.valueOf(new Random().nextInt(1000));
Object value = new Random().nextInt(1000);
int expire = new Random().nextInt(100);
clq.add(key);
cacheUtil.put(key, value, expire);
System.out.println(atomicInteger.incrementAndGet() +
". Cache successful ,key=" + key +
",value=" + value +
",expire=" + expire);
count1.countDown();
}
});
}
try {

count1.await();// Wait for all put After execution 
} catch (InterruptedException e) {

e.printStackTrace();
}
// Test concurrency get performance 
atomicInteger.set(0);
for (int i = 0; i < LOOP_TIMES; i++) {

es.execute(new Runnable() {

@Override
public void run() {

try {

// Random waiting time 
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {

e.printStackTrace();
}
String key = clq.poll();
System.out.println(atomicInteger.incrementAndGet() +
". From the cache key=" + key +
" Value :" + cacheUtil.get(key));
count2.countDown();
}
});
}
try {

count2.await();// Wait for all get After execution 
} catch (InterruptedException e) {

e.printStackTrace();
}
es.shutdown();
while (true){

if (es.isTerminated()){

System.out.println(" All tasks have been carried out ");
System.out.println(" Cache size :" + cacheUtil.size());
return;
}
}
}

The final test was very good , There was no mistake , Some of the test results are shown below :

Four 、 expand

This class simply implements the caching process , But it doesn’t always work well in practice , First of all, its capacity must be limited , Can’t save too much cache , Because it uses JVM Memory in the heap , If optimized, direct memory can be used for storage , Secondly, its function is relatively simple , For example, it doesn’t support LRU Elimination, etc , This can be done with a double linked list +Map Or is it LinkedHashMap To achieve , More functions can be expanded .

My official account of WeChat north wind IT The way Better browsing experience , Here are more excellent articles for you , Pay attention to it !

发表回复