修复StampedCache类get方法并发问题(issue#IBCIQG@Gitee)

This commit is contained in:
Looly
2024-12-21 00:46:34 +08:00
parent 63c3777c70
commit 738e965a06
8 changed files with 59 additions and 41 deletions

View File

@@ -198,7 +198,7 @@ public class StrictBeanDesc extends AbstractBeanDesc {
private static Method getGetterForBoolean(final Method[] gettersOrSetters, final String fieldName, final boolean ignoreCase) { private static Method getGetterForBoolean(final Method[] gettersOrSetters, final String fieldName, final boolean ignoreCase) {
// 查找isXXX // 查找isXXX
return MethodUtil.getMethod(gettersOrSetters, m -> { return MethodUtil.getMethod(gettersOrSetters, m -> {
if (0 != m.getParameterCount() || false == BooleanUtil.isBoolean(m.getReturnType())) { if (0 != m.getParameterCount() || !BooleanUtil.isBoolean(m.getReturnType())) {
// getter方法要求无参数且返回boolean或Boolean // getter方法要求无参数且返回boolean或Boolean
return false; return false;
} }
@@ -231,7 +231,7 @@ public class StrictBeanDesc extends AbstractBeanDesc {
private static Method getSetterForBoolean(final Method[] gettersOrSetters, final String fieldName, final boolean ignoreCase) { private static Method getSetterForBoolean(final Method[] gettersOrSetters, final String fieldName, final boolean ignoreCase) {
// 查找isXXX // 查找isXXX
return MethodUtil.getMethod(gettersOrSetters, m -> { return MethodUtil.getMethod(gettersOrSetters, m -> {
if (1 != m.getParameterCount() || false == BooleanUtil.isBoolean(m.getParameterTypes()[0])) { if (1 != m.getParameterCount() || !BooleanUtil.isBoolean(m.getParameterTypes()[0])) {
// setter方法要求1个boolean或Boolean参数 // setter方法要求1个boolean或Boolean参数
return false; return false;
} }

View File

@@ -51,12 +51,12 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
@Override @Override
public boolean containsKey(final K key) { public boolean containsKey(final K key) {
return null != get(key, false, false); return null != doGet(key, false, false);
} }
@Override @Override
public V get(final K key, final boolean isUpdateLastAccess) { public V get(final K key, final boolean isUpdateLastAccess) {
return get(key, isUpdateLastAccess, true); return doGet(key, isUpdateLastAccess, true);
} }
@Override @Override
@@ -106,19 +106,38 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
} }
/** /**
* 获取值 * 获取值,使用乐观锁,但是此方法可能导致读取脏数据,但对于缓存业务可容忍。情况如下:
* <pre>
* 1. 读取时无写入,不冲突,直接获取值
* 2. 读取时无写入,但是乐观读时触发了并发异常,此时获取同步锁,获取新值
* 4. 读取时有写入,此时获取同步锁,获取新值
* </pre>
* *
* @param key 键 * @param key 键
* @param isUpdateLastAccess 是否更新最后修改时间 * @param isUpdateLastAccess 是否更新最后修改时间
* @param isUpdateCount 是否更新命中数get时更新contains时不更新 * @param isUpdateCount 是否更新命中数get时更新contains时不更新
* @return 值或null * @return 值或null
*/ */
private V get(final K key, final boolean isUpdateLastAccess, final boolean isUpdateCount) { private V doGet(final K key, final boolean isUpdateLastAccess, final boolean isUpdateCount) {
// 尝试读取缓存,使用乐观读锁 // 尝试读取缓存,使用乐观读锁
CacheObj<K, V> co = null;
long stamp = lock.tryOptimisticRead(); long stamp = lock.tryOptimisticRead();
CacheObj<K, V> co = getWithoutLock(key); boolean isReadError = true;
if (false == lock.validate(stamp)) { if(lock.validate(stamp)){
// 有写线程修改了此对象,悲观读 try{
// 乐观读,可能读取脏数据,在缓存中可容忍,分两种情况
// 1. 读取时无线程写入
// 2. 读取时有线程写入,导致数据不一致,此时读取未更新的缓存值
co = getWithoutLock(key);
isReadError = false;
} catch (final Exception ignore){
// ignore
}
}
if(isReadError){
// 转换为悲观读
// 原因可能为无锁读时触发并发异常,或者锁被占(正在写)
stamp = lock.readLock(); stamp = lock.readLock();
try { try {
co = getWithoutLock(key); co = getWithoutLock(key);
@@ -133,7 +152,7 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
missCount.increment(); missCount.increment();
} }
return null; return null;
} else if (false == co.isExpired()) { } else if (!co.isExpired()) {
if (isUpdateCount) { if (isUpdateCount) {
hitCount.increment(); hitCount.increment();
} }
@@ -159,7 +178,7 @@ public abstract class StampedCache<K, V> extends AbstractCache<K, V> {
if (null == co) { if (null == co) {
return null; return null;
} }
if (false == co.isExpired()) { if (!co.isExpired()) {
// 首先尝试获取值,如果值存在且有效,返回之 // 首先尝试获取值,如果值存在且有效,返回之
if (isUpdateCount) { if (isUpdateCount) {
hitCount.increment(); hitCount.increment();

View File

@@ -152,29 +152,30 @@ public class DateBetween implements Serializable {
final Calendar endCal = CalendarUtil.calendar(end); final Calendar endCal = CalendarUtil.calendar(end);
final int result = endCal.get(Calendar.YEAR) - beginCal.get(Calendar.YEAR); final int result = endCal.get(Calendar.YEAR) - beginCal.get(Calendar.YEAR);
if (false == isReset) { if(isReset){
final int beginMonthBase0 = beginCal.get(Calendar.MONTH); return result;
final int endMonthBase0 = endCal.get(Calendar.MONTH); }
if (beginMonthBase0 < endMonthBase0) {
return result; final int beginMonthBase0 = beginCal.get(Calendar.MONTH);
} else if (beginMonthBase0 > endMonthBase0) { final int endMonthBase0 = endCal.get(Calendar.MONTH);
return result - 1; if (beginMonthBase0 < endMonthBase0) {
} else if (Calendar.FEBRUARY == beginMonthBase0 return result;
&& CalendarUtil.isLastDayOfMonth(beginCal) } else if (beginMonthBase0 > endMonthBase0) {
&& CalendarUtil.isLastDayOfMonth(endCal)) { return result - 1;
// 考虑闰年的2月情况 } else if (Calendar.FEBRUARY == beginMonthBase0
// 两个日期都位于2月的最后一天此时月数按照相等对待此时都设置为1号 && CalendarUtil.isLastDayOfMonth(beginCal)
beginCal.set(Calendar.DAY_OF_MONTH, 1); && CalendarUtil.isLastDayOfMonth(endCal)) {
endCal.set(Calendar.DAY_OF_MONTH, 1); // 考虑闰年的2月情况
} // 两个日期都位于2月的最后一天此时月数按照相等对待此时都设置为1号
beginCal.set(Calendar.DAY_OF_MONTH, 1);
endCal.set(Calendar.YEAR, beginCal.get(Calendar.YEAR)); endCal.set(Calendar.DAY_OF_MONTH, 1);
final long between = endCal.getTimeInMillis() - beginCal.getTimeInMillis(); }
if (between < 0) {
return result - 1; endCal.set(Calendar.YEAR, beginCal.get(Calendar.YEAR));
} final long between = endCal.getTimeInMillis() - beginCal.getTimeInMillis();
if (between < 0) {
return result - 1;
} }
return result;
} }
/** /**

View File

@@ -221,7 +221,7 @@ public class FileTypeUtil {
* @throws IORuntimeException 读取文件引起的异常 * @throws IORuntimeException 读取文件引起的异常
*/ */
public static String getType(final File file, final boolean isExact) throws IORuntimeException { public static String getType(final File file, final boolean isExact) throws IORuntimeException {
if (false == FileUtil.isFile(file)) { if (!FileUtil.isFile(file)) {
throw new IllegalArgumentException("Not a regular file!"); throw new IllegalArgumentException("Not a regular file!");
} }
InputStream in = null; InputStream in = null;

View File

@@ -584,9 +584,7 @@ public class FileUtil {
* @since 5.8.28 * @since 5.8.28
*/ */
public static int getTotalLines(final File file, final int bufferSize) { public static int getTotalLines(final File file, final int bufferSize) {
if (false == isFile(file)) { Assert.isTrue(isFile(file), ()-> new IORuntimeException("Input must be a File"));
throw new IORuntimeException("Input must be a File");
}
try (final LineCounter lineCounter = new LineCounter(getInputStream(file), bufferSize)) { try (final LineCounter lineCounter = new LineCounter(getInputStream(file), bufferSize)) {
return lineCounter.getCount(); return lineCounter.getCount();
} catch (final IOException e) { } catch (final IOException e) {
@@ -778,7 +776,7 @@ public class FileUtil {
* @since 4.5.5 * @since 4.5.5
*/ */
public static boolean cleanEmpty(final File directory) throws IORuntimeException { public static boolean cleanEmpty(final File directory) throws IORuntimeException {
if (directory == null || false == directory.exists() || false == directory.isDirectory()) { if (directory == null || !directory.exists() || !directory.isDirectory()) {
return true; return true;
} }

View File

@@ -201,7 +201,7 @@ public class JschSftp extends AbstractFtp {
* @since 4.1.14 * @since 4.1.14
*/ */
public ChannelSftp getClient() { public ChannelSftp getClient() {
if (false == this.channel.isConnected()) { if (!this.channel.isConnected()) {
init(); init();
} }
return this.channel; return this.channel;

View File

@@ -89,7 +89,7 @@ public class JschUtil {
public static Channel createChannel(final Session session, final ChannelType channelType, final long timeout) { public static Channel createChannel(final Session session, final ChannelType channelType, final long timeout) {
final Channel channel; final Channel channel;
try { try {
if (false == session.isConnected()) { if (!session.isConnected()) {
session.connect((int) timeout); session.connect((int) timeout);
} }
channel = session.openChannel(channelType.getValue()); channel = session.openChannel(channelType.getValue());

View File

@@ -59,7 +59,7 @@ public class UserAgentParser {
// issue#IA74K2 MACOS下的微信不属于移动平台 // issue#IA74K2 MACOS下的微信不属于移动平台
if(platform.isMobile() || browser.isMobile()){ if(platform.isMobile() || browser.isMobile()){
if(false == os.isMacOS()){ if(!os.isMacOS()){
userAgent.setMobile(true); userAgent.setMobile(true);
} }
} }