!1432 fix: 修复 EnumUtil 递归更新异常 + Cache 高并发双重检查锁逻辑缺陷

Merge pull request !1432 from Busyliu/v5-dev
This commit is contained in:
Looly
2026-03-02 09:59:44 +00:00
committed by Gitee
9 changed files with 82 additions and 12 deletions

View File

@@ -134,8 +134,12 @@ public abstract class AbstractCache<K, V> implements Cache<K, V> {
// issue#3686 由于这个方法内的加锁是get独立锁不和put锁互斥而put和pruneCache会修改cacheMap导致在pruneCache过程中get会有并发问题
// 因此此处需要使用带全局锁的get获取值
v = get(key, isUpdateLastAccess);
v = supplier.callWithRuntimeException();
put(key, v, timeout);
// fix issue#IDQGP2: 双重检查后若缓存已有值则直接返回不再调用supplier
// 原实现忽略了双重检查的结果,导致高并发下缓存值被重复覆盖
if (null == v) {
v = supplier.callWithRuntimeException();
put(key, v, timeout);
}
} finally {
keyLock.unlock();
keyLockMap.remove(key);

View File

@@ -26,11 +26,11 @@ public class BoundedPriorityQueue<E> extends PriorityQueue<E>{
/**
* 构造
* @param capacity 容量
* @param capacity 容量必须大于0
* @param comparator 比较器
*/
public BoundedPriorityQueue(int capacity, final Comparator<? super E> comparator) {
super(capacity, (o1, o2) -> {
super(capacity <= 0 ? 1 : capacity, (o1, o2) -> {
int cResult;
if(comparator != null) {
cResult = comparator.compare(o1, o2);
@@ -53,6 +53,10 @@ public class BoundedPriorityQueue<E> extends PriorityQueue<E>{
*/
@Override
public boolean offer(E e) {
if (capacity <= 0) {
// 容量为0或负数时不接受任何元素
return false;
}
if(size() >= capacity) {
E head = peek();
if (this.comparator().compare(e, head) <= 0){

View File

@@ -127,7 +127,7 @@ public class TemporalUtil {
/**
* 偏移到指定的周几
*
* @param temporal 日期或者日期时间
* @param temporal 日期或者日期时间,为{@code null}返回{@code null}
* @param dayOfWeek 周几
* @param <T> 日期类型如LocalDate或LocalDateTime
* @param isPrevious 是否向前偏移,{@code true}向前偏移,{@code false}向后偏移。
@@ -135,7 +135,10 @@ public class TemporalUtil {
* @since 5.8.0
*/
@SuppressWarnings("unchecked")
public <T extends Temporal> T offset(T temporal, DayOfWeek dayOfWeek, boolean isPrevious) {
public static <T extends Temporal> T offset(T temporal, DayOfWeek dayOfWeek, boolean isPrevious) {
if (null == temporal) {
return null;
}
return (T) temporal.with(isPrevious ? TemporalAdjusters.previous(dayOfWeek) : TemporalAdjusters.next(dayOfWeek));
}
}

View File

@@ -33,7 +33,8 @@ public class Tuple extends CloneSupport<Tuple> implements Iterable<Object>, Seri
* @param members 成员数组
*/
public Tuple(Object... members) {
this.members = members;
// defensive copy保证 Tuple 的不可变性,防止外部修改传入数组影响内部状态
this.members = members.clone();
}
/**
@@ -51,10 +52,11 @@ public class Tuple extends CloneSupport<Tuple> implements Iterable<Object>, Seri
/**
* 获得所有元素
*
* @return 获得所有元素
* @return 获得所有元素的副本
*/
public Object[] getMembers() {
return this.members;
// 返回副本而非内部数组引用,防止外部修改破坏 Tuple 的不可变性
return this.members.clone();
}
/**

View File

@@ -621,6 +621,9 @@ public class Money implements Serializable, Comparable<Money> {
* @return 相除后的结果。
*/
public Money divide(double val) {
if (val == 0) {
throw new ArithmeticException("Division by zero");
}
return newMoneyWithSameCurrency(Math.round(cent / val));
}
@@ -635,7 +638,10 @@ public class Money implements Serializable, Comparable<Money> {
* @return 累除后的结果。
*/
public Money divideBy(double val) {
this.cent = Math.round(this.cent / val);
if (val == 0) {
throw new ArithmeticException("Division by zero");
} this.cent = Math.round(this.cent / val);
return this;
}

View File

@@ -582,6 +582,9 @@ public class ArrayUtil extends PrimitiveArrayUtil {
*/
public static Object copy(Object src, int srcPos, Object dest, int destPos, int length) {
//noinspection SuspiciousSystemArraycopy
if (null == src || null == dest) {
throw new NullPointerException("Source array and destination array must not be null");
}
System.arraycopy(src, srcPos, dest, destPos, length);
return dest;
}
@@ -598,6 +601,9 @@ public class ArrayUtil extends PrimitiveArrayUtil {
*/
public static Object copy(Object src, Object dest, int length) {
//noinspection SuspiciousSystemArraycopy
if (null == src || null == dest) {
throw new NullPointerException("Source array and destination array must not be null");
}
System.arraycopy(src, 0, dest, 0, length);
return dest;
}

View File

@@ -480,6 +480,16 @@ public class EnumUtil {
if (null == enumClass) {
return null;
}
return CACHE.computeIfAbsent(enumClass, (k) -> enumClass.getEnumConstants());
// fix issue#IDQYJK: 避免 ConcurrentHashMap.computeIfAbsent 在枚举类静态初始化时
// 递归调用导致 IllegalStateException: Recursive update 的问题
// 使用 get + putIfAbsent 替代 computeIfAbsent安全支持递归场景
Enum<?>[] enums = CACHE.get(enumClass);
if (null == enums) {
enums = enumClass.getEnumConstants();
if (null != enums) {
CACHE.putIfAbsent(enumClass, enums);
}
}
return enums;
}
}

View File

@@ -70,6 +70,20 @@ public class EnumUtilTest {
assertEquals("type1", enumMap.get("TEST1"));
}
/**
* 测试枚举类静态初始化中调用 EnumUtil 不会导致 Recursive update 异常
* fix issue#IDQYJK
*/
@Test
public void getFieldValuesRecursiveTest() {
// SelfRefEnum 在静态初始化时调用了 EnumUtil.getNames
// 修复前会抛出 IllegalStateException: Recursive update
// 修复后应正常返回结果
List<Object> values = EnumUtil.getFieldValues(SelfRefEnum.class, "label");
assertNotNull(values);
assertEquals(3, values.size());
}
public enum TestEnum{
TEST1("type1"), TEST2("type2"), TEST3("type3");
@@ -89,4 +103,24 @@ public class EnumUtilTest {
return this.name;
}
}
/**
* 静态初始化中使用 EnumUtil 的枚举,用于测试 fix issue#IDQYJK
*/
public enum SelfRefEnum {
A("labelA"), B("labelB"), C("labelC");
// 静态初始化块中调用 EnumUtil触发 ConcurrentHashMap.computeIfAbsent 的递归场景
static final List<String> NAMES = EnumUtil.getNames(SelfRefEnum.class);
private final String label;
SelfRefEnum(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
}

View File

@@ -246,7 +246,8 @@ public class RC4 implements Serializable {
}
for (int i = 0; i < SBOX_LENGTH; i++) {
j = (j + sbox[i] + (key[i % key.length]) & 0xFF) % SBOX_LENGTH;
// fix: 运算符优先级修正,& 0xFF 应先与 key 字节值进行位与运算,再参与加法,避免有符号 byte 导致计算偏差
j = (j + sbox[i] + (key[i % key.length] & 0xFF)) % SBOX_LENGTH;
swap(i, j, sbox);
}
return sbox;