add smart http support

This commit is contained in:
Looly
2024-12-25 14:20:24 +08:00
parent 2af90ebaa5
commit 7f12d45b4e
15 changed files with 362 additions and 219 deletions

View File

@@ -34,7 +34,7 @@ import java.util.LinkedHashMap;
* @param <V> 值类型
* @author Looly
*/
public class FIFOCache<K, V> extends ReentrantCache<K, V> {
public class FIFOCache<K, V> extends LockedCache<K, V> {
private static final long serialVersionUID = 1L;
/**
@@ -55,7 +55,7 @@ public class FIFOCache<K, V> extends ReentrantCache<K, V> {
public FIFOCache(final int capacity, final long timeout) {
this.capacity = capacity;
this.timeout = timeout;
cacheMap = new LinkedHashMap<>(capacity + 1, 1.0f, false);
this.cacheMap = new LinkedHashMap<>(capacity + 1, 1.0f, false);
}
/**

View File

@@ -16,8 +16,10 @@
package org.dromara.hutool.core.cache.impl;
import java.util.HashMap;
import org.dromara.hutool.core.thread.lock.NoLock;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
/**
* LFU(least frequently used) 最少使用率缓存<br>
@@ -31,7 +33,7 @@ import java.util.Iterator;
* @param <K> 键类型
* @param <V> 值类型
*/
public class LFUCache<K, V> extends StampedCache<K, V> {
public class LFUCache<K, V> extends LockedCache<K, V> {
private static final long serialVersionUID = 1L;
/**
@@ -56,7 +58,10 @@ public class LFUCache<K, V> extends StampedCache<K, V> {
this.capacity = capacity;
this.timeout = timeout;
cacheMap = new HashMap<>(capacity + 1, 1.0f);
//lock = new ReentrantLock();
//cacheMap = new HashMap<>(capacity + 1, 1.0f);
lock = NoLock.INSTANCE;
cacheMap = new ConcurrentHashMap<>(capacity + 1, 1.0f);
}
// ---------------------------------------------------------------- prune

View File

@@ -20,6 +20,7 @@ import org.dromara.hutool.core.lang.mutable.Mutable;
import org.dromara.hutool.core.map.FixedLinkedHashMap;
import java.util.Iterator;
import java.util.concurrent.locks.ReentrantLock;
/**
* LRU (least recently used)最近最久未使用缓存<br>
@@ -33,7 +34,7 @@ import java.util.Iterator;
* @param <K> 键类型
* @param <V> 值类型
*/
public class LRUCache<K, V> extends ReentrantCache<K, V> {
public class LRUCache<K, V> extends LockedCache<K, V> {
private static final long serialVersionUID = 1L;
/**
@@ -65,6 +66,7 @@ public class LRUCache<K, V> extends ReentrantCache<K, V> {
listener.onRemove(entry.getKey().get(), entry.getValue().getValue());
}
});
lock = new ReentrantLock();
cacheMap = fixedLinkedHashMap;
}

View File

@@ -19,24 +19,24 @@ package org.dromara.hutool.core.cache.impl;
import org.dromara.hutool.core.collection.iter.CopiedIter;
import java.util.Iterator;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 使用{@link ReentrantLock}保护的缓存读写都使用悲观锁完成主要避免某些Map无法使用读写锁的问题<br>
* 使用{@link Lock}保护的缓存读写都使用悲观锁完成主要避免某些Map无法使用读写锁的问题<br>
* 例如使用了LinkedHashMap的缓存由于get方法也会改变Map的结构因此读写必须加互斥锁
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.7.15
*/
public abstract class ReentrantCache<K, V> extends AbstractCache<K, V> {
public abstract class LockedCache<K, V> extends AbstractCache<K, V> {
private static final long serialVersionUID = 1L;
/**
* 一些特殊缓存例如使用了LinkedHashMap的缓存由于get方法也会改变Map的结构导致无法使用读写锁
*/
protected final ReentrantLock lock = new ReentrantLock();
protected Lock lock = new ReentrantLock();
@Override
public void put(final K key, final V object, final long timeout) {

View File

@@ -1,203 +0,0 @@
/*
* Copyright (c) 2013-2024 Hutool Team and hutool.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.dromara.hutool.core.cache.impl;
import org.dromara.hutool.core.collection.iter.CopiedIter;
import java.util.Iterator;
import java.util.concurrent.locks.StampedLock;
/**
* 使用{@link StampedLock}保护的缓存,使用读写乐观锁<br>
* 使用乐观锁有效的提高的缓存性能,但是无法避免脏读问题
*
* @param <K> 键类型
* @param <V> 值类型
* @author looly
* @since 5.7.15
*/
public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
private static final long serialVersionUID = 1L;
/**
* 乐观锁,此处使用乐观锁解决读多写少的场景<br>
* get时乐观读再检查是否修改修改则转入悲观读重新读一遍可以有效解决在写时阻塞大量读操作的情况。<br>
* see: https://www.cnblogs.com/jiagoushijuzi/p/13721319.html
*/
protected final StampedLock lock = new StampedLock();
@Override
public void put(final K key, final V object, final long timeout) {
final long stamp = lock.writeLock();
try {
putWithoutLock(key, object, timeout);
} finally {
lock.unlockWrite(stamp);
}
}
@Override
public boolean containsKey(final K key) {
return null != doGet(key, false, false);
}
@Override
public V get(final K key, final boolean isUpdateLastAccess) {
return doGet(key, isUpdateLastAccess, true);
}
@Override
public Iterator<CacheObj<K, V>> cacheObjIterator() {
CopiedIter<CacheObj<K, V>> copiedIterator;
final long stamp = lock.readLock();
try {
copiedIterator = CopiedIter.copyOf(cacheObjIter());
} finally {
lock.unlockRead(stamp);
}
return new CacheObjIterator<>(copiedIterator);
}
@Override
public final int prune() {
final long stamp = lock.writeLock();
try {
return pruneCache();
} finally {
lock.unlockWrite(stamp);
}
}
@Override
public void remove(final K key) {
final long stamp = lock.writeLock();
CacheObj<K, V> co;
try {
co = removeWithoutLock(key);
} finally {
lock.unlockWrite(stamp);
}
if (null != co) {
onRemove(co.key, co.obj);
}
}
@Override
public void clear() {
final long stamp = lock.writeLock();
try {
cacheMap.clear();
} finally {
lock.unlockWrite(stamp);
}
}
/**
* 获取值,使用乐观锁,但是此方法可能导致读取脏数据,但对于缓存业务可容忍。情况如下:
* <pre>
* 1. 读取时无写入,不冲突,直接获取值
* 2. 读取时无写入,但是乐观读时触发了并发异常,此时获取同步锁,获取新值
* 3. 读取时有写入,此时获取同步锁,获取新值
* </pre>
*
* @param key 键
* @param isUpdateLastAccess 是否更新最后修改时间
* @param isUpdateCount 是否更新命中数get时更新contains时不更新
* @return 值或null
*/
private V doGet(final K key, final boolean isUpdateLastAccess, final boolean isUpdateCount) {
// 尝试读取缓存,使用乐观读锁
CacheObj<K, V> co = null;
long stamp = lock.tryOptimisticRead();
boolean isReadError = true;
if(lock.validate(stamp)){
try{
// 乐观读,可能读取脏数据,在缓存中可容忍,分两种情况
// 1. 读取时无线程写入
// 2. 读取时有线程写入,导致数据不一致,此时读取未更新的缓存值
co = getWithoutLock(key);
isReadError = false;
} catch (final Exception ignore){
// ignore
}
}
if(isReadError){
// 转换为悲观读
// 原因可能为无锁读时触发并发异常,或者锁被占(正在写)
stamp = lock.readLock();
try {
co = getWithoutLock(key);
} finally {
lock.unlockRead(stamp);
}
}
// 未命中
if (null == co) {
if (isUpdateCount) {
missCount.increment();
}
return null;
} else if (!co.isExpired()) {
if (isUpdateCount) {
hitCount.increment();
}
return co.get(isUpdateLastAccess);
}
// 悲观锁,二次检查
return getOrRemoveExpired(key, isUpdateCount);
}
/**
* 同步获取值,如果过期则移除之
*
* @param key 键
* @param isUpdateCount 是否更新命中数get时更新contains时不更新
* @return 有效值或null
*/
private V getOrRemoveExpired(final K key, final boolean isUpdateCount) {
final long stamp = lock.writeLock();
CacheObj<K, V> co;
try {
co = getWithoutLock(key);
if (null == co) {
return null;
}
if (!co.isExpired()) {
// 首先尝试获取值,如果值存在且有效,返回之
if (isUpdateCount) {
hitCount.increment();
}
return co.getValue();
}
// 无效移除
co = removeWithoutLock(key);
if (isUpdateCount) {
missCount.increment();
}
} finally {
lock.unlockWrite(stamp);
}
if (null != co) {
onRemove(co.key, co.obj);
}
return null;
}
}

View File

@@ -19,9 +19,15 @@ package org.dromara.hutool.core.cache.impl;
import org.dromara.hutool.core.cache.GlobalPruneTimer;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.lang.mutable.Mutable;
import org.dromara.hutool.core.thread.lock.NoLock;
import java.util.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.locks.ReentrantLock;
/**
* 定时缓存<br>
@@ -33,7 +39,7 @@ import java.util.concurrent.ScheduledFuture;
* @param <K> 键类型
* @param <V> 值类型
*/
public class TimedCache<K, V> extends StampedCache<K, V> {
public class TimedCache<K, V> extends LockedCache<K, V> {
private static final long serialVersionUID = 1L;
/** 正在执行的定时任务 */
@@ -57,6 +63,8 @@ public class TimedCache<K, V> extends StampedCache<K, V> {
public TimedCache(final long timeout, final Map<Mutable<K>, CacheObj<K, V>> map) {
this.capacity = 0;
this.timeout = timeout;
// 如果使用线程安全的Map则不加锁否则默认使用ReentrantLock
this.lock = map instanceof ConcurrentMap ? NoLock.INSTANCE : new ReentrantLock();
this.cacheMap = Assert.isNotInstanceOf(LinkedHashMap.class, map);
}

View File

@@ -33,7 +33,7 @@ import java.util.concurrent.ScheduledFuture;
* @param <K> 键类型
* @param <V> 值类型
*/
public class TimedReentrantCache<K, V> extends ReentrantCache<K, V> {
public class TimedReentrantCache<K, V> extends LockedCache<K, V> {
private static final long serialVersionUID = 1L;
/** 正在执行的定时任务 */

View File

@@ -22,8 +22,6 @@ import org.dromara.hutool.core.lang.mutable.Mutable;
import org.dromara.hutool.core.lang.ref.Ref;
import org.dromara.hutool.core.map.reference.WeakConcurrentMap;
import java.util.WeakHashMap;
/**
* 弱引用缓存<br>
* 对于一个给定的键,其映射的存在并不阻止垃圾回收器对该键的丢弃,这就使该键成为可终止的,被终止,然后被回收。<br>
@@ -44,7 +42,7 @@ public class WeakCache<K, V> extends TimedCache<K, V>{
* @param timeout 超时时常,单位毫秒,-1或0表示无限制
*/
public WeakCache(final long timeout) {
super(timeout, new WeakHashMap<>());
super(timeout, new WeakConcurrentMap<>());
}
@Override