При изучении параллелизма в Java примеры в книге основаны на кешах, поэтому я хочу написать общий локальный кеш
написать впереди
Чтобы написать кеш, вам необходимо учитывать базовую структуру хранения кеша, истечение срока действия кеша, аннулирование кеша, одновременное чтение и запись и т. д. Таким образом, локальный кеш, написанный вами, будет разработан с учетом этих моментов.
инвалидация кеша
Аннулирование кеша означает, что срок действия кеша истек, и данные кеша с истекшим сроком действия необходимо удалить. Удаление можно разделить на активное удаление и пассивное удаление.
Активно удалять
- Удалить регулярно
При установке времени истечения срока действия пары ключ-значение создайте таймер и позвольте таймеру немедленно удалить ключ, когда наступит время истечения срока действия ключа.
преимущество: наиболее бережно относится к памяти, убедитесь, что пары ключ-значение с истекшим сроком действия удалены в максимально возможной степени, и освободите память, занимаемую парами ключ-значение с истекшим сроком действия.
недостаток:правильноCPUНедружелюбно, если просроченных пар ключ-значение слишком много, удаление просроченных пар ключ-значение займет значительную частьCPUвремя исполнения - регулярно удалять
Выполняйте операцию удаления ключа с истекшим сроком действия через равные промежутки времени и уменьшайте эффект операции удаления, ограничивая продолжительность и частоту операции удаления.CPUВлияние времени [сложность, трудно установить время выполнения и частоту]
пассивное удаление
- ленивое удаление
Срок действия ключей проверяется только при их извлечении. Плюсы и минусы напротив временного удаления
преимущество:правильноCPUдружелюбно
недостаток: не поддерживает память Используя ленивое удаление + периодическое удаление одновременно, вы можете получитьCPUи баланс памяти, поэтому аннулирование кеша локального кеша принимает два вида ленивого удаления + обычное удаление
удаление кеша
Устранение кеша означает, что когда количество кешей достигает определенного значения, определенные данные удаляются в соответствии с определенными правилами, независимо от того, истек ли срок действия данных. Общие алгоритмы удаления кеша:
- алгоритм "первым пришел - первым вышел"
FIFO】
Первые данные, сохраненные в кеше, будут удалены первыми. - Наименее часто используемый алгоритм
LFU】
Удалите данные с наименьшим количеством использований.Общая реализация заключается в подсчете всех данных и расчете их один раз при каждом использовании.Устраните данные с наименьшим количеством подсчетов. - Наименее недавно использованный алгоритм
LRU】
Недавние данные без использования первого для устранения, обычно достигаются в списке, самое последнее посещение, вновь вставленный элемент переместился в головку списка, из списка последнего элемента Отбор локального кешаLRUАлгоритм устранения кеша
Определение структуры кэша
После выбора алгоритмов инвалидации и ликвидации кеша можно определить структуру кеша.Первоначально учитывалась безопасность потоков.K-VструктурныйConcurrentHashMapПлюс + структура двусвязного списка, но Хэ Тяньтянь в последнее время пристрастился к запоминанию английских слов, и выучил заодноLinkedHashMapможет быть реализованLRU, используется ленивоLinkedHashMap.LinkedHashMapОн может храниться в соответствии с порядком вставки [по умолчанию] или в соответствии с порядком доступа [сначала будет помещено последнее прочитанное, а в конец — наименее часто прочитанное]. порядок хранения для хранения порядка доступа только должен быть будетaccssOrderУстановить какtrueОК, по умолчаниюfalse. в то же времяLinkedHashMapПредоставляет метод для определения необходимости удаления наименее часто читаемых данных [removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest)вернуть по умолчаниюfalseНе удалять], вам нужно удалить и переопределить этот метод.
Определение узла кэша
public class CacheNode<K, V> {
/**
* 保存的键
*/
private K key;
/**
* 保存的值
*/
private V value;
/**
* 保存时间
*/
private long gmtCreate;
/**
* 过期时间,单位为毫秒,默认永久有效
*/
private long expireTime = Long.MAX_VALUE;
}
Инициализация структуры кэша
/**
* 底层缓存结构
*/
private LinkedHashMap<K, CacheNode<K, V>> localCache;
/**
* 负载因子
*/
private final float DEFAULT_LOAD_FACTOR = 0.75f;
/**
* 缓存过期清理策略
*/
private ExpireStrategy<K, V> lazyExpireStrategy = new LazyExpireStrategy<>();
private ExpireStrategy<K, V> regularExpireStrategy;
private int maxCacheSie;
/**
* 构造函数
*
* @param expireStrategy 缓存失效策略实现类,针对的是定期失效缓存,传入null,定期失效缓存类为默认配置值
* @param maxCacheSie 缓存最大允许存放的数量,缓存失效策略根据这个值触发
*/
public LocalCache(int maxCacheSize, ExpireStrategy<K, V> expireStrategy) {
//缓存最大容量为初始化的大小
this.maxCacheSize = maxCacheSize;
//缓存最大容量 => initialCapacity * DEFAULT_LOAD_FACTOR,避免扩容操作
int initialCapacity = (int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1;
//accessOrder设置为true,根据访问顺序而不是插入顺序
this.localCache = new LinkedHashMap<K, CacheNode<K, V>>(initialCapacity, DEFAULT_LOAD_FACTOR, true) {
@Override
protected boolean removeEldestEntry(Map.Entry<K, CacheNode<K, V>> eldest) {
return size() > maxCacheSie;
}
};
this.regularExpireStrategy = (expireStrategy == null ? new RegularExpireStrategy<>() : expireStrategy);
//启动定时清除过期键任务
regularExpireStrategy.removeExpireKey(localCache, null);
}
инструкция:
- переписанный
removeEldestEntryметод, когда размер кеша превышает установленныйmaxCacheSizeудаляйте только те элементы, которые редко используются - установить в конструкторе
accessOrderзаtrue, Абсолютный доступ к последовательному хранилищу - Размер кеша определяется
(int) Math.ceil(maxCacheSie / DEFAULT_LOAD_FACTOR) + 1рассчитано так, что даже если множествоmaxCacheSizeне будет запускать операцию расширения -
regularExpireStrategy.removeExpireKey(localCache, null);Инициировать задачу периодического удаления
Периодически удалять реализации политики:
public class RegularExpireStrategy<K, V> implements ExpireStrategy<K, V> {
Logger logger = LoggerFactory.getLogger(getClass());
/**
* 定期任务每次执行删除操作的次数
*/
private long executeCount = 100;
/**
* 定期任务执行时常 【1分钟】
*/
private long executeDuration = 1000 * 60;
/**
* 定期任务执行的频率
*/
private long executeRate = 60;
//get and set
public long getExecuteCount() {
return executeCount;
}
public void setExecuteCount(long executeCount) {
this.executeCount = executeCount;
}
public long getExecuteDuration() {
return executeDuration;
}
public void setExecuteDuration(long executeDuration) {
this.executeDuration = executeDuration;
}
public long getExecuteRate() {
return executeRate;
}
public void setExecuteRate(long executeRate) {
this.executeRate = executeRate;
}
/**
* 清空过期Key-Value
*
* @param localCache 本地缓存底层使用的存储结构
* @param key 缓存的键
* @return 过期的值
*/
@Override
public V removeExpireKey(LinkedHashMap<K, CacheNode<K, V>> localCache, K key) {
logger.info("开启定期清除过期key任务");
ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
//定时周期任务,executeRate分钟之后执行,默认1小时执行一次
executor.scheduleAtFixedRate(new MyTask(localCache), 0, executeRate, TimeUnit.MINUTES);
return null;
}
/**
* 自定义任务
*/
private class MyTask<K, V> implements Runnable {
private LinkedHashMap<K, CacheNode<K, V>> localCache;
public MyTask(LinkedHashMap<K, CacheNode<K, V>> localCache) {
this.localCache = localCache;
}
@Override
public void run() {
long start = System.currentTimeMillis();
List<K> keyList = localCache.keySet().stream().collect(Collectors.toList());
int size = keyList.size();
Random random = new Random();
for (int i = 0; i < executeCount; i++) {
K randomKey = keyList.get(random.nextInt(size));
if (localCache.get(randomKey).getExpireTime() - System.currentTimeMillis() < 0) {
logger.info("key:{}已过期,进行定期删除key操作", randomKey);
localCache.remove(randomKey);
}
//超时执行退出
if (System.currentTimeMillis() - start > executeDuration) {
break;
}
}
}
}
}
инструкция:
- использовать
ScheduledExecutorServiceизscheduleAtFixedRateРеализуйте запланированные периодические задачи - По умолчанию выполняется один раз в час, а время каждого выполнения 1 минута, и каждой случайной попытки удалить 100 элементов [если позволяет время, срок действия ключа истекает]
Реализация стратегии удаления с ленивой загрузкой: LazyExpireStrategy.java
public class LazyExpireStrategy<K, V> implements ExpireStrategy<K, V> {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* 清空过期Key-Value
*
* @param localCache 本地缓存底层使用的存储结构
* @param key 缓存的键
* @return 过期的值
*/
@Override
public V removeExpireKey(LinkedHashMap<K, CacheNode<K, V>> localCache, K key) {
CacheNode<K, V> baseCacheValue = localCache.get(key);
//值不存在
if (baseCacheValue == null) {
logger.info("key:{}对应的value不存在", key);
return null;
} else {
//值存在并且未过期
if (baseCacheValue.getExpireTime() - System.currentTimeMillis() > 0) {
return baseCacheValue.getValue();
}
}
logger.info("key:{}已过期,进行懒删除key操作", key);
localCache.remove(key);
return null;
}
}
инструкция:
- Определить, существует ли ключ, если нет, вернуть
nullценность - Если ключ существует, оцените, истек ли срок его действия, верните без истечения срока действия, удалите его после истечения срока действия и верните
nullценность
Реализация метода работы с кэшем
- Удалить
public synchronized V removeKey(K key) { CacheNode<K, V> cacheNode = localCache.remove(key); return cacheNode != null ? cacheNode.getValue() : null; } - найти
При поиске лениво будет удалять стратегиюpublic synchronized V getValue(K key) { return lazyExpireStrategy.removeExpireKey(localCache, key); } - депозит
Сохраненное значение не имеет срока действия:
Сохраненное значение неверно:public synchronized V putValue(K key, V value) { CacheNode<K, V> cacheNode = new CacheNode<>(); cacheNode.setKey(key); cacheNode.setValue(value); localCache.put(key, cacheNode); // 返回添加的值 return value; }public synchronized V putValue(K key, V value, long expireTime) { CacheNode<K, V> cacheNode = new CacheNode<>(); cacheNode.setKey(key); cacheNode.setValue(value); cacheNode.setGmtCreate(System.currentTimeMillis() + expireTime); localCache.put(key, cacheNode); // 返回添加的值 return value; } - Установить время истечения кэша
public synchronized void setExpireKey(K key, long expireTime) { if (localCache.get(key) != null) { localCache.get(key).setExpireTime(System.currentTimeMillis() + expireTime); } } - получить размер кеша
public synchronized int getLocalCacheSize() { return localCache.size(); }
Все методы используются для обеспечения безопасности потоковsynchronizeКлючевое слово [Безопасность потока, He Tiantian будет толькоsynchronize, я не думал о других лучших методах блокировки, рассматривал блокировки чтения-записи, но это не сработало,]
Поза
- Создать объект localcace
- поза один
Первый параметр — это размер кеша, который позволяет хранить количество кешей. Второй параметр периодически удаляет объект, если онLocalCache<Integer, Integer> localCache = new LocalCache<>(4, null);null, используйте периодическое удаление объектов по умолчанию [цикл выполнения, время выполнения и время выполнения — все значения по умолчанию] - поза два
Передать настраиваемый объект периодического удаленияRegularExpireStrategy<Integer, Integer> expireStrategy = new RegularExpireStrategy<>(); expireStrategy.setExecuteRate(1); //每隔1分钟执行一次 LocalCache<Integer, Integer> localCache = new LocalCache<>(4, expireStrategy);
- поза один
- хранить в кэше
for (int i = 0; i < 16; i++) { localCache.putValue(i, i); } - Сохранить в кеше и установить срок действия
localCache.putValue(i, i,1000); - прочитать значение из кеша
localCache.getValue(i) - Установите время истечения срока действия данных в существующем кеше
localCache.setExpireKey(i, 1000) - Получить размер кеша
localCache.getLocalCacheSize() - удалить кеш
localCache.removeKey(i)
Локальный кеш пишется с целью обучения, что рекомендуется в практических приложениях.GoogleизGuava Cache, если вы достаточно уверены в моем коде, вы, конечно же, можете использоватьBug
точка оптимизации
- использовать
ConcurrentHashMapплюс + двусвязный список - Инвалидация кеша и выбор времени для запланированных задач разнообразны. В настоящее время единицей аннулирования кеша по умолчанию являются миллисекунды, а единицей запланированных задач по умолчанию — минуты. Путем добавления в метод
TimeUnitБольше разнообразных вариантов времени параметра - Поддержка параллелизма плохая, а реализация — это синхронизация [He Tiantian слишком плох! ! ! 】
TODO
- Загрузите частные серверы, предоставьте зависимости для использования в других проектах и узнайте, как загружать частные серверы.
Наконец прикрепил:Полный код проекта,Добро пожаловатьfork,star
如有错误,欢迎指正交流【何甜甜真的太菜了! ! ! 】