mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-07-21 15:09:48 +08:00
修复StampedCache类get方法并发问题(issue#IBCIQG@Gitee)
This commit is contained in:
@@ -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;
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
|
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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;
|
||||||
|
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
@@ -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());
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user