底层并发扩容思想是“分段预判 + 无锁协作 + 状态隔离”,通过预判总量、主线程预扩容、工作线程分段写入、count统一管理实现无锁平滑增长,避免ByteArrayOutputStream多线程写入时的count错乱与buf覆盖。
底层并发扩容思想不是简单加锁扩数组,而是“分段预判 + 无锁协作 + 状态隔离”,在多线程写入场景下避免竞争抖动。它不依赖单次 synchronized 扩容,而是通过提前划分写入边界、复用系统类的线程安全接口、结合字节流特性实现真正平滑的动态增长。
理解 ByteArrayOutputStream 在并发下的真实瓶颈
ByteArrayOutputStream 本身不是线程安全的——它的 count 和 buf 都是普通字段,多线程 write() 会引发数据覆盖或越界异常。但它的 ensureCapacity() 方法(JDK 9+)是无状态的:只改 buf,不依赖 count,因此可在写入前由单一协调线程安全调用。
- 错误做法:多个线程同时调用 write() → count 错乱、buf 覆盖
- 可行路径:一个线程负责容量预估与 ensureCapacity,其他线程只做 write,且 write 前确保不超限
- 关键前提:写入总量可预判(如 HTTP Content-Length、文件 size、序列化预估)
用分段写入 + 容量预分配实现无锁协作
不靠 synchronized 或 ReentrantLock 包裹整个 write 流程,而是把“扩容决策”和“数据写入”解耦:
- 主线程或调度器先获取总大小(如 Files.size(path) 或 conn.getHeaderField("Content-Length")),调用 baos.ensureCapacity(totalSize)
- 将总字节范围划分为若干块(如每块 64KB),每个工作线程负责一段偏移区间的写入
- 各线程通过 baos.write(byte[], off, len) 写入自己分到的数据段——该方法内部只操作 buf[off…off+len),只要不越界,无需额外同步
- 最终 count 由主线程统一累加或通过 AtomicLong 管理,避免 write() 中 count++ 竞争
配合系统类构建可伸缩的字节容器
单纯用 ByteArrayOutputStream 不足以支撑高并发字节聚合,需组合其他系统类增强能力:
- 使用 ByteBuffer.allocateDirect() + slice() 替代 baos:适合大块字节拼接,支持多线程写入不同 slice,底层内存独立,无扩容开销
- 用 ByteArrayOutputStream + ByteArrayOutputStream 的装饰器模式:封装一个 ConcurrentByteArrayOutputStream,内部持有一个 volatile byte[] buf 和 AtomicInteger count,write 时 CAS 更新 count,扩容由 CAS 失败触发并交由专用扩容线程执行
- 对接系统监控做弹性伸缩:通过 JMX 获取当前 JVM direct memory 使用率,若 >70%,自动切换为基于 FileChannel 的临时磁盘缓冲,避免 OOM
实战建议:从预判到落地的三步闭环
真正的平滑扩容不在代码行数,而在节奏控制:
- 预判阶段:优先读取元信息(Content-Length、file.length()、Protobuf.getSerializedSize()),而非试探性读取;不可预判时,采用两阶段估算——先读前 4KB,按密度外推全量
- 分配阶段:ensureCapacity 传入值应向上取整到页对齐(如 4096 的倍数),减少内存碎片;小数据(
- 写入阶段:禁用单字节 write(int b),强制使用 write(byte[], off, len);若必须逐字节写,用 ThreadLocal<ByteArrayOutputStream> 隔离线程上下文

















