Java 21虚拟线程下Guava Striped Locks替代方案探讨
分段锁在Java 21虚拟线程环境的适用性
分段锁的核心是通过拆分锁粒度降低竞争,这一设计逻辑在Java 21虚拟线程环境下依然完全适用:
- 虚拟线程虽轻量,但锁竞争导致的阻塞(即使是虚拟线程的park/unpark)仍会影响并发效率,分段锁通过分散竞争点,能有效提升吞吐量。
- 需注意虚拟线程数量可能远多于平台线程,若分段数设置过小,仍可能出现同一分段下大量虚拟线程阻塞的情况,建议根据业务访问热点或CPU核心数调整分段数(例如CPU核心数的2~4倍)。
- 虚拟线程的调度特性并未改变分段锁平衡并发度与内存开销的设计初衷,它仍是该场景下的有效优化手段。
标准库替代方案与自定义代码分析
基础替代方案(固定锁数组)
如果追求最简单的实现,可直接用固定数量的锁数组,通过key的哈希值映射到对应锁:
import java.util.concurrent.Callable; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class BasicStripedLocks { private final ReentrantReadWriteLock[] locks; public BasicStripedLocks(int stripes) { locks = new ReentrantReadWriteLock[stripes]; for (int i = 0; i < stripes; i++) { locks[i] = new ReentrantReadWriteLock(); } } public BasicStripedLocks() { this(Runtime.getRuntime().availableProcessors() * 2); } public final <T> T read(Object key, Callable<T> readOperation) throws Exception { Lock lock = locks[Math.abs(key.hashCode()) % locks.length].readLock(); lock.lock(); try { return readOperation.call(); } finally { lock.unlock(); } } public final <T> T write(Object key, Callable<T> writeOperation) throws Exception { Lock lock = locks[Math.abs(key.hashCode()) % locks.length].writeLock(); lock.lock(); try { return writeOperation.call(); } finally { lock.unlock(); } } }
该方案实现简单、线程安全,但锁会长期占用内存,适合访问热点相对固定的场景。
自定义StripedLocks代码的评价
你的代码通过弱引用+引用队列实现锁的自动回收,思路巧妙,适合访问热点动态变化的场景,以下是具体分析:
优点
- 用弱引用自动回收无人使用的锁,避免固定数组方案的内存浪费。
- 封装了读写锁的调用逻辑,对外接口简洁易用。
- 引用队列的清理逻辑能及时移除失效的锁引用,避免内存泄漏。
潜在问题与优化点
- 哈希取模的负数问题:
keyObject.hashCode() % stripes可能得到负数,建议改为Math.abs(keyObject.hashCode()) % stripes,避免ConcurrentHashMap出现负数key。 purgeStaleLocks的同步块:同步queue对象会带来额外竞争,且reference.get()可能返回null(弱引用已被回收),可优化为:
@SuppressWarnings("unchecked") void purgeStaleLocks() { Reference<LockHolder> reference; while ((reference = (Reference<LockHolder>) queue.poll()) != null) { LockHolder holder = reference.get(); if (holder != null) { locks.remove(holder.key()); } } }
- 默认分段数设置:默认值4在高并发场景下易导致竞争集中,建议默认值设为
Runtime.getRuntime().availableProcessors() * 2,或让用户明确配置。 getLock的compute逻辑优化:每次获取锁都调用compute会带来额外CAS开销,可先尝试get获取,不存在再用computeIfAbsent:
private ReentrantReadWriteLock getLock(Object keyObject) { Objects.requireNonNull(keyObject,"keyObject can't be null"); int key = Math.abs(keyObject.hashCode()) % stripes; WeakReference<LockHolder> ref = locks.get(key); if (ref != null) { LockHolder holder = ref.get(); if (holder != null) { return holder.lock(); } } return locks.computeIfAbsent(key, k -> { LockHolder holder = new LockHolder(k, new ReentrantReadWriteLock()); return new WeakReference<>(holder, queue); }).get().lock(); }
- 虚拟线程适配:当前代码逻辑与虚拟线程兼容,但需合理设置分段数,避免同一分段下大量虚拟线程阻塞带来的调度开销。
内容的提问来源于stack exchange,提问作者Hristo Stoyanov
相关产品推荐
相关产品推荐

