避免全局大锁拖垮吞吐量,核心是让线程各干各的:按业务维度拆分独立缓存实例(如usercache、configcache、tokencache),高频写场景再做key级哈希分片(如16分片),禁用全量遍历与隐式阻塞操作,读多写少时叠加读写分离。

避免全局大锁拖垮吞吐量,核心是让线程尽量“各干各的”——不抢同一把锁。关键不在加不加锁,而在锁住什么、锁多大、锁多久。
按业务维度拆分独立缓存实例
不同数据类型访问模式、生命周期、更新频率差异大,硬塞进一个 ConcurrentHashMap 容易形成热点桶竞争。比如用户会话、系统配置、临时令牌这三类数据混在一起,写 TokenCache 的线程可能频繁触发整个 map 的扩容,间接卡住读 ConfigCache 的请求。
- 分别定义 UserCache、ConfigCache、TokenCache,每个都持有一个独立的
ConcurrentHashMap实例 - 写操作只锁定各自 map 内部对应哈希桶,跨域访问完全无锁冲突
- 清理策略也解耦:TokenCache 可配 TTL 自动过期,不影响 ConfigCache 的稳定性
对高频写场景做 key 级哈希分片
单个缓存实例仍扛不住百万级用户 ID 并发写入时,可在应用层模拟分段机制,进一步分散锁点。
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- 用
key.hashCode() & 0xF得到 0–15 共 16 个分片号(推荐 2 的幂次,分布更均匀) - 维护一个大小为 16 的数组:
ConcurrentHashMap<k v>[] shards</k> -
put时先算分片号,再操作对应 shard;锁仅作用于该子 map 的桶内节点 - 分片数不宜过多(如超过 64),否则哈希分布不均、内存开销上升
避开隐式全局锁陷阱
即使用了 ConcurrentHashMap,某些操作仍会破坏并发性,看似无锁,实则暗藏阻塞。
- 禁用
keySet()/values()/entrySet()全量遍历——这些操作虽不显式加锁,但可能触发扩容或结构重排,干扰并发写 - 清理过期数据别用定时线程反复 scan 整个 map,改用时间轮或每个分片自带的 LRU 队列
- 慎用
computeIfAbsent做复杂初始化:若内部含 I/O 或耗时计算,应提前移出同步块,只在真正需要写入时加锁
读多写少时叠加读写分离
ConcurrentHashMap 的 get 是无锁 volatile 读,但若整个缓存视图需强一致性(如配置中心),可补一层读写锁,释放读并发压力。
- 对整个缓存结构加
ReentrantReadWriteLock,读走readLock,写走writeLock - 适用于读频极高、写极少的场景(如发布变更才更新配置)
- 注意:不能和 ConcurrentHashMap 原生桶锁混用——读写锁保护的是整体视图,不是替代细粒度写锁
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










