Java并发-JDK并发容器
并发编程中,会使用到容器类。JDK本身提供了一些常用且基础的容器类,很有必要了解一下。
# 一、旧时代的同步容器
在Java 1.5之前,线程安全的容器类实现相对简单,性能方面也差强人意,但我们还是要了解一下。
Vector
。线程安全的ArrayList
,所有方法都用synchronized
修饰,达到线程安全的目的HashTable
。线程安全的HashMap
,所有方法都用synchronized
修饰,达到线程安全的目的Collections.synchronizedXxx
工厂方法。返回的容器类内部使用synchronized
关键字实现线程安全
# 二、新时代的并发容器
Java 1.5之后,利用CAS
指令(乐观锁)提供了更高效的并发容器。
# 1、非阻塞队列
ConcurrentLinkedQueue
。基于链表的无界线程安全队列,采用非阻塞算法,高并发环境下性能最好的队列。适用场景:高并发的生产者-消费者模型,不需要阻塞等待的场景
# 2、写时复制容器
CopyOnWriteArrayList
。只在写时加锁的ArrayList
,采用写时复制(Copy-On-Write)的思想,读性能大幅提升。适用场景:读多写少的场景,如配置、黑白名单等CopyOnWriteArraySet
。基于CopyOnWriteArrayList
实现的Set
,同样适用于读多写少的场景
# 3、阻塞队列
BlockingQueue
。一个阻塞式的数据共享通道,参考:阻塞队列BlockingQueue。主要实现有:ArrayBlockingQueue
。基于数组的有界阻塞队列,必须指定容量LinkedBlockingQueue
。基于链表的可选有界阻塞队列,不指定容量时为Integer.MAX_VALUE
LinkedBlockingDeque
。基于链表的双向阻塞队列,支持从两端插入和移除元素PriorityBlockingQueue
。支持优先级排序的无界阻塞队列DelayQueue
。支持延迟获取元素的无界阻塞队列SynchronousQueue
。不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作
# 4、并发Map
ConcurrentMap
。线程安全的Map
接口,主要实现有:ConcurrentHashMap
。高并发哈希表,采用分段锁(JDK 1.7)或CAS+synchronized(JDK 1.8+)实现,适用于高并发的键值对存储ConcurrentSkipListMap
。基于跳表实现的有序并发Map
,提供有序的遍历。相比TreeMap
的红黑树实现,跳表的插入和删除只需要对局部数据进行操作,避免了全局锁的使用
# 三、核心设计思想
# 1、分段锁(Segment Locking)
ConcurrentHashMap
在JDK 1.7中利用了分而治之的思想,使用分段锁(Segment)技术。将整个哈希表分为多个段,每个段独立加锁,这样不同段的操作可以并发进行,大大提高了并发性能。JDK 1.8之后改为使用CAS操作和synchronized
锁定桶头节点,进一步优化了性能。
# 2、写时复制(Copy-On-Write)
CopyOnWriteArrayList
采用了写时复制的思想:
- 读操作不加锁,直接读取当前数组
- 写操作会复制一份新的数组,在新数组上修改,然后将引用指向新数组
- 在迭代频率远高于容器修改时,性能大大增强
- 缺点是内存占用较大,且数据一致性是最终一致性
# 3、非阻塞算法
ConcurrentLinkedQueue
使用非阻塞算法(lock-free),基于CAS操作实现线程安全,避免了锁的开销,在高并发场景下性能优异。
# 四、使用示例
# 1、ConcurrentHashMap示例
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
// 线程安全的put操作
map.put("key1", 1);
// 原子性的putIfAbsent
map.putIfAbsent("key2", 2);
// 原子性的replace
map.replace("key1", 1, 10);
// 并发遍历
map.forEach((k, v) -> System.out.println(k + ": " + v));
# 2、CopyOnWriteArrayList示例
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
// 写操作会复制整个数组
list.add("item1");
// 读操作不加锁
String item = list.get(0);
// 迭代器是快照,不会抛出ConcurrentModificationException
for (String s : list) {
list.add("newItem"); // 不会影响当前迭代
}
# 3、BlockingQueue示例
BlockingQueue<String> queue = new LinkedBlockingQueue<>(100);
// 生产者
queue.put("item"); // 队列满时阻塞
queue.offer("item", 5, TimeUnit.SECONDS); // 超时等待
// 消费者
String item = queue.take(); // 队列空时阻塞
String item2 = queue.poll(5, TimeUnit.SECONDS); // 超时等待
# 五、选择建议
- 读多写少场景:使用
CopyOnWriteArrayList
或CopyOnWriteArraySet
- 高并发键值对存储:使用
ConcurrentHashMap
- 需要有序遍历的并发Map:使用
ConcurrentSkipListMap
- 生产者-消费者模型:
- 需要阻塞:使用
BlockingQueue
的各种实现 - 不需要阻塞:使用
ConcurrentLinkedQueue
- 需要阻塞:使用
- 任务调度:使用
DelayQueue
或PriorityBlockingQueue
# 六、分布式集合
分布式环境下要使用集合,可以考虑Redisson (opens new window)。
我在Java集合文章中有所介绍,请移步查看。
# 七、总结
工欲善其事,必先利其器。新时代的Java并发容器能较好且安全地解决多线程并发问题,需要好好地掌握它!
祝你变得更强!
编辑 (opens new window)
上次更新: 2025/08/15