concurrenthashmap通过分段锁(jdk 7)或cas+synchronized(jdk 8+)实现高并发线程安全,核心是缩小锁粒度:jdk 7将哈希表分为16个segment独立加锁,读操作无锁;jdk 8废弃segment,锁细化至桶头节点,并引入红黑树与多线程扩容。

用 synchronized 保护大文件分片上传的索引状态,核心不是“锁整个上传过程”,而是精准锁定**分片元数据的读写临界区**——比如记录已接收分片序号的集合、合并标志位、临时文件路径映射等。直接对方法或对象粗粒度加锁会导致严重性能瓶颈,尤其在高并发上传场景下。
只同步真正共享且可变的状态操作
分片上传中真正需要同步的通常是内存中的索引结构(如 ConcurrentHashMap 存储各文件 ID 对应的已收分片 Set),而非整个上传逻辑。若强行用 synchronized(this) 或 synchronized 方法包装 uploadChunk(),所有请求会串行排队,吞吐量断崖式下降。
- ✅ 推荐:为每个文件 ID 创建独立锁对象,按 fileID 分片加锁
- ✅ 推荐:用
ConcurrentHashMap+ 原子操作(如computeIfAbsent)管理分片集合,减少显式同步范围 - ❌ 避免:在 Controller 层或 Service 方法上直接加
synchronized
用 fileID 作为锁粒度,避免全局竞争
不同用户、不同文件的上传互不影响,锁应该隔离到单个文件维度。典型做法是维护一个锁池(如 ConcurrentHashMap<string object></string>),首次访问某 fileID 时创建专属锁对象并复用:
// 锁池:fileId → 锁对象
private final ConcurrentHashMap<string object> lockPool = new ConcurrentHashMap();
public void onReceiveChunk(String fileId, int chunkIndex) {
Object lock = lockPool.computeIfAbsent(fileId, k -> new Object());
synchronized (lock) {
// 更新该文件的已收分片集合(如 TreeSet 或 BitSet)
Set<integer> received = indexMap.computeIfAbsent(fileId, k -> ConcurrentHashMap.newKeySet());
received.add(chunkIndex);
// 检查是否收齐,触发合并(此检查必须在锁内完成,防止重复合并)
if (received.size() == expectedTotalChunks) {
triggerMerge(fileId);
}
}
// 锁释放后才清理锁对象(需配合引用计数或定时清理,防内存泄漏)
}</integer></string>
状态合并阶段必须原子校验+标记
多个线程可能同时发现“分片收齐”,若不加控制,会多次执行 mergeFile(),导致文件损坏或磁盘写冲突。需在同步块内完成“检查→标记→执行”三步:
- 用
ConcurrentHashMap<string atomicboolean></string>记录是否已触发合并(mergedFlags.putIfAbsent(fileId, new AtomicBoolean(false))) - 或在索引 Map 中存一个枚举状态(
PENDING / MERGING / MERGED),更新时用compareAndSet - 合并完成后,主动从 lockPool 中移除对应锁对象(建议配合 ScheduledExecutorService 定期清理空闲锁)
注意:synchronized 不解决分布式场景问题
上述方案仅适用于单机部署。若服务集群部署(Nginx 负载多台 Java 实例),synchronized 失效,必须升级为分布式锁(如 Redis 的 SETNX + Lua 脚本,或 ZooKeeper 临时顺序节点)。此时 fileID 仍是关键 key,但锁由外部中间件保障。
本地开发或单体架构下,合理使用 synchronized 配合细粒度锁对象,完全能安全、高效地保护分片索引状态——关键是锁什么、锁多久、谁来持有它。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











