同步容器解决多线程并发读写原始集合导致的数据不一致、数组越界、ConcurrentModificationException等问题,通过synchronized对每个公有方法加锁实现单方法原子性,但无法保证多方法组合操作的线程安全。

Java里使用同步容器,核心目的是在多线程环境下保障集合操作的基本线程安全性——即避免因并发读写导致的数据不一致、数组越界、ConcurrentModificationException等运行时异常。但它不是为高并发设计的“终极方案”,而是一种简单、粗粒度的保护机制。
同步容器解决的是什么问题
Java原始集合类(如ArrayList、HashMap、HashSet)本身不加锁,多线程同时修改会出错。例如:
- 两个线程同时对
ArrayList执行add(),可能造成元素覆盖或size计数错误 - 一个线程正在遍历
HashMap,另一个线程触发扩容,可能引发死循环(JDK 7)或数据丢失(JDK 8+)
同步容器通过封装+内置锁,让这些风险在方法调用层面被屏蔽。
同步容器怎么实现线程安全
本质是用synchronized对每个公有方法加锁,确保同一时刻只有一个线程能执行该方法:
立即学习“Java免费学习笔记(深入)”;
-
Vector:所有方法(add()、get()、size())都带synchronized修饰符 -
Hashtable:同理,put()、get()、containsKey()全同步 -
Collections.synchronizedList(new ArrayList()):返回一个包装器对象,内部用传入的list作为锁对象,代理调用并加锁
锁对象通常是容器实例本身,所以多个线程竞争的是同一个锁。
同步容器的局限性不能忽视
它只保“单个方法”原子,不保“多个方法组合”的逻辑正确性:
- 迭代过程非原子:即使用了
Vector,for(int i=0; i仍可能抛 ArrayIndexOutOfBoundsException——因为size()和get(i)之间可能被其他线程删了元素 - 条件操作需手动加锁:比如“如果不存在就添加”,
map.containsKey(k) || map.put(k,v)不是原子的,必须用synchronized(map){...}包起来 - 性能瓶颈明显:所有读写串行化,高并发下大量线程阻塞等待,吞吐量急剧下降
这也是为什么ConcurrentHashMap、CopyOnWriteArrayList等并发容器在JDK 5之后成为更主流的选择。
什么时候还能用同步容器
适用场景其实很窄,但仍有价值:
- 线程数少、并发压力低(比如后台管理工具、配置缓存)
- 需要快速验证逻辑,不想引入复杂并发结构
- 遗留系统中已有大量
Vector/Hashtable调用,短期无法重构 - 配合客户端加锁做简单复合操作(如上文
getLast()加synchronized(list))
基本上就这些。同步容器是线程安全的起点,不是终点;理解它,是为了更清楚地知道什么时候该换用并发容器。










