java流本身非线程安全,加锁关键在于隔离共享资源:只读场景应各建流实例;多写需用reentrantlock同步操作;读写缓冲区宜用readwritelock;自定义流须保护内部状态。

Java 中多线程并发读写流(如 InputStream/OutputStream、Reader/Writer,或自定义缓存流)本身不是线程安全的,JDK 标准流类基本不保证多线程并发访问的安全性。因此,“并发读写流怎么加锁”,核心不是给流对象加锁,而是明确读写场景、隔离共享资源、按需选择锁策略。
明确“读写流”的实际含义
很多开发者说的“读写流”其实指以下几种常见情况:
- 多个线程同时读同一个输入流(如从文件或网络读配置)
- 多个线程同时写同一个输出流(如日志写入同一
FileOutputStream) - 一个线程读、另一个线程写共享的内存缓冲区或缓存流(如
ByteArrayInputStream+ByteArrayOutputStream配合使用) - 自定义流包装类,内部维护共享状态(如统计字节数、标记位置、缓存 buffer)
⚠️ 注意:System.in/System.out 是全局静态对象,多线程直接调用 read()/write() 会竞争;但 BufferedInputStream 等装饰器类本身不处理线程同步,需外部保障。
按场景选择加锁方式
✅ 场景一:多个线程只读同一个流(如只读配置文件)
- 流本身不可变(如
new FileInputStream("config.txt")),且不调用mark()/reset()等改变状态的方法 - 无需加锁:每个线程应创建独立的流实例(流是轻量级的,打开后底层 fd 才真正占用资源)
- ❌ 错误做法:多个线程共用一个
FileInputStream实例并并发read()→ 可能读到错乱字节、IOException或跳过数据
示例正确写法:
Java Maven Common下载Java Maven 项目审查工作流的通用输入处理。当 Java Maven 项目以 ZIP压缩包或 GitLab 仓库 URL 形式提供且需要...时使用。
// 每次读都新建流,线程安全 Runnable reader = () -> { try (FileInputStream fis = new FileInputStream("config.txt")) { fis.readAllBytes(); // 安全 } catch (IOException e) { /* ... */ } };
✅ 场景二:多个线程写同一个输出目标(如日志文件)
- 共享一个
FileOutputStream或PrintWriter - 必须串行化写操作,否则内容交错、损坏
推荐方式:
- 使用
ReentrantLock(比synchronized更灵活,支持超时、可中断) - 或直接用
synchronized块,锁住统一对象(如logLock)
private final ReentrantLock logLock = new ReentrantLock();
private final FileOutputStream fos;
public void writeLog(byte[] data) {
logLock.lock();
try {
fos.write(data);
fos.flush();
} catch (IOException e) {
// handle
} finally {
logLock.unlock();
}
}
⚠️ 切勿锁
fos对象本身(synchronized(fos))——FileOutputStream内部无同步设计,且可能被其他无关代码意外锁住,引发隐蔽死锁。
✅ 场景三:读写共享缓冲区(如 byte[] buffer 或 ConcurrentLinkedQueue<byte></byte>)
- 若多个线程既读又写同一块内存缓冲区(非标准流,而是你封装的“流式”数据结构)
- 此时不是锁流,而是锁缓冲区状态
适用方案:
- 读多写少 → 用
ReentrantReadWriteLock- 读线程拿
readLock(),允许多个并发读 - 写线程拿
writeLock(),独占修改 buffer
- 读线程拿
- 读写频率接近 → 直接用
ReentrantLock或synchronized
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private byte[] sharedBuffer = new byte[8192];
public int readFromBuffer(int offset, int len) {
rwLock.readLock().lock();
try {
return System.arraycopy(sharedBuffer, offset, ..., len);
} finally {
rwLock.readLock().unlock();
}
}
public void writeToBuffer(byte[] src) {
rwLock.writeLock().lock();
try {
// 安全覆盖或追加
System.arraycopy(src, 0, sharedBuffer, 0, Math.min(src.length, sharedBuffer.length));
} finally {
rwLock.writeLock().unlock();
}
}
✅ 场景四:需要“读写分离 + 线程安全流”抽象(高级需求)
- 如实现一个线程安全的
CircularByteBuffer、带统计的CountingInputStream - 关键原则:把状态变量(pos、count、mark、buffer 等)保护好
- 推荐组合:
-
volatile字段(如closed标志) -
AtomicInteger(如字节计数器) -
ReentrantLock或ReadWriteLock控制临界区 - 避免在持有锁时调用用户传入的回调(防止锁外阻塞或死锁)
-
不推荐的做法(易出错)
- 在
synchronized(this)中包装流操作 → 若该类被继承或暴露this,锁对象不可控 - 对
InputStream调用synchronized(input)→ 无意义,InputStream类没做同步,且锁对象不统一 - 在读锁中尝试获取写锁(读锁升级)→
ReentrantReadWriteLock明确禁止,会死锁 - 忘记
flush()或close()导致数据滞留 → 这不是锁问题,但常与并发混淆
小结:关键决策点
| 你的需求 | 推荐锁方式 | 说明 |
|---|---|---|
| 多线程只读同一文件 | 不加锁,各建各的流 | 最简单安全 |
| 多线程写同一日志文件 |
ReentrantLock 或 synchronized(lockObj)
|
锁粒度=整个写操作,避免交错 |
| 读多写少的共享 buffer | ReentrantReadWriteLock |
提升读并发,写仍独占 |
| 自定义流类含内部状态 | 组合 volatile + AtomicXxx + Lock
|
按字段访问模式精细化控制 |
不复杂但容易忽略:锁的不是“流”,而是“对流的操作行为”和“背后共享的状态”。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











