semaphore用于限制并发更新索引状态的线程数(如最多3个),避免多线程竞争导致索引错乱;它比synchronized或reentrantlock更灵活,支持“n进”语义,适合索引更新轻、数据写入重的场景。

Java中用Semaphore保护大文件分片并行写入的索引状态,核心是**限制同时更新索引的线程数,避免多线程竞争导致索引错乱或越界**。它不直接锁文件,而是控制对共享索引变量(如当前写入位置、分片序号、偏移量映射表等)的访问权限。
为什么用Semaphore而不是synchronized或ReentrantLock
当并发写入线程较多(比如100个分片由10个线程轮询处理),但只需保证**最多N个线程能同时修改索引状态**(例如最多3个线程可同步更新全局偏移量),Semaphore比独占锁更灵活:
- synchronized/ReentrantLock 是“1进1出”,无法表达“最多3个可进”的语义
- Semaphore(3) 可让3个线程并行更新索引,其余等待,提升吞吐又防冲突
- 适合“写索引轻、写数据重”的场景:索引更新快,数据落盘慢,无需全程串行
典型索引状态与Semaphore配合方式
常见需保护的索引状态包括:全局写入偏移量(fileOffset)、已分配分片ID计数器、分片-偏移量映射缓存(Map
- 每个分片任务在写入前先acquire()获取一个许可,确保此时只有≤N个线程在操作索引
- 读取当前fileOffset → 计算本分片起始位置 → 更新fileOffset += 分片大小 → release()
- 即使多个线程几乎同时读到相同旧值,因acquire阻塞了超额线程,实际更新顺序可控
代码关键片段(带注释)
// 全局共享:允许最多2个线程并发更新索引
private static final Semaphore INDEX_UPDATE_SEM = new Semaphore(2);
// 全局偏移量(volatile保障可见性,非原子性更新仍需同步)
private static volatile long fileOffset = 0L;
public long allocateOffset(int chunkSize) throws InterruptedException {
INDEX_UPDATE_SEM.acquire(); // 进入索引临界区
try {
long offset = fileOffset;
fileOffset += chunkSize;
return offset;
} finally {
INDEX_UPDATE_SEM.release(); // 务必释放,避免死锁
}
}
注意事项与避坑点
- 不要在acquire后长时间执行耗时操作(如写磁盘、网络调用),否则许可被占满,其他线程卡住——索引更新逻辑必须轻量
- 若索引状态涉及多个变量(如offset + map.put()),需确保整个操作在同一个acquire/release内完成,不可拆开
- 配合volatile字段使用时,注意volatile只保可见性,不保复合操作原子性;所以仍需Semaphore兜底
- 建议设置超时acquire(timeout, TimeUnit),防止某个线程异常卡死导致整组任务停滞
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











