bufferedreader 不是线程安全的,多个线程共用同一实例并发调用 readline() 会导致数据错乱、阻塞、空指针及读取位置错位等问题,因其内部缓冲区和指针未加锁保护。

BufferedReader 本身不是线程安全的,多个线程共用同一个 BufferedReader 实例并发调用 readLine(),极易引发数据错乱、阻塞、空指针甚至读取位置错位等问题。
为什么 readLine() 并发调用会出问题
BufferedReader 内部维护一个字符缓冲区(char[] buf)和读取位置指针(nextChar、markedChar 等)。这些字段是实例变量,没有加锁保护。当多个线程同时调用 readLine():
- 它们会竞争修改同一组指针和缓冲区状态,导致某一线程读到“半截行”或跳过部分数据;
- 可能出现一个线程刚把缓冲区填满,另一个线程立即清空或重置它,造成逻辑混乱;
- 若底层流(如 SocketInputStream 或 FileInputStream)本身也非线程安全,问题会进一步放大;
- 常见现象包括:某次
readLine()返回null(误判 EOF),或抛出NullPointerException(因内部字段被并发破坏),或无限阻塞(因缓冲区状态异常无法推进)。
典型错误写法与表现
以下代码在多线程环境下非常危险:
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 多个线程共享 br,并各自调用: String line = br.readLine(); // ❌ 共享实例 + 并发调用 = 不安全
实际表现可能包括:
- 服务端日志中出现孤立的
null输出(如 Web Server 收到 favicon.ico 请求后,主线程未处理完就退出); - 客户端反复发送消息,服务端只收到部分或乱序内容;
- 某个线程卡在
readLine()不返回,而其他线程仍在尝试读取,最终连接超时或资源泄漏。
安全的替代方案
核心原则:**避免共享 BufferedReader 实例**。推荐做法如下:
- 每个线程独占一个 BufferedReader:为每个 Socket 连接或文件读取任务单独创建 BufferedReader,确保生命周期与线程绑定;
-
用 synchronized 封装读操作(不推荐高频场景):若必须复用,需对整个
readLine()调用加锁,但会严重降低吞吐量; -
改用线程安全的替代方式:例如使用
Scanner(注意其内部也非完全线程安全,仍需隔离)、或直接操作InputStream+ 手动按行解析(配合read()或read(byte[])); -
现代方案优先考虑 NIO:用
AsynchronousSocketChannel或Selector配合 ByteBuffer,天然规避阻塞和线程争用问题。
排查与验证技巧
发现疑似并发读取异常时,可快速确认:
- 检查所有
BufferedReader创建点,确认是否被多个Thread或Runnable实例共用; - 在
readLine()前后添加日志,打印线程名(Thread.currentThread().getName())和当前读取行号,观察是否出现交叉或跳变; - 用
jstack抓取线程堆栈,看是否有多个线程停在BufferedReader.readLine的同一行; - 单元测试中模拟多线程并发读取小文件,对比单线程结果,验证行数和内容一致性。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











