1. 概述
Java 的 集合框架 是语言的核心组件之一,提供了丰富的接口和实现类,支持我们以直观的方式创建和操作各种集合。
在单线程环境下使用普通集合非常简单,但在多线程(并发)环境中,直接使用非同步集合可能会导致数据不一致、竞态条件等严重问题。
为此,Java 提供了强大的支持:通过 Collections
类中的多个静态方法,我们可以为已有集合创建线程安全的同步包装器(synchronization wrappers)。
这些包装器能够快速地将任意集合转换为线程安全版本,适用于大多数并发场景。
在本文中,我们将深入探讨这些静态同步包装器,并对比同步集合与并发集合之间的区别。
2. synchronizedCollection() 方法
我们首先介绍的是 synchronizedCollection()
方法。顾名思义,它返回一个由指定 Collection
支持的线程安全集合。
来看一个简单的示例:
Collection<Integer> syncCollection = Collections.synchronizedCollection(new ArrayList<>());
Runnable listOperations = () -> {
syncCollection.addAll(Arrays.asList(1, 2, 3, 4, 5, 6));
};
Thread thread1 = new Thread(listOperations);
Thread thread2 = new Thread(listOperations);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
assertThat(syncCollection.size()).isEqualTo(12);
✅ 创建一个同步集合就是这么简单粗暴。
上面代码中,我们启动了两个线程同时向集合中添加元素。由于使用了同步包装器,最终集合大小为 12,说明线程安全得到了保障。
⚠️ 注意:虽然集合本身是线程安全的,但迭代操作仍需手动加锁。
3. synchronizedList() 方法
类似地,我们可以使用 synchronizedList()
方法来创建一个线程安全的 List
实例。
List<Integer> syncList = Collections.synchronizedList(new ArrayList<>());
这个方法的用法和 synchronizedCollection()
几乎一模一样。
但如果我们要对同步 List 进行迭代操作,就必须显式使用 synchronized
块来确保操作的原子性:
List<String> syncCollection = Collections.synchronizedList(Arrays.asList("a", "b", "c"));
List<String> uppercasedCollection = new ArrayList<>();
Runnable listOperations = () -> {
synchronized (syncCollection) {
syncCollection.forEach((e) -> {
uppercasedCollection.add(e.toUpperCase());
});
}
};
⚠️ 踩坑提醒:对同步集合进行迭代时,必须加锁,否则会抛出 ConcurrentModificationException
。
4. synchronizedMap() 方法
Collections
类还提供了 synchronizedMap()
方法,用于创建线程安全的 Map
实例:
Map<Integer, String> syncMap = Collections.synchronizedMap(new HashMap<>());
✅ 使用方式和 List/Set 类似,同样适用于多线程环境下的 Map 操作。
5. synchronizedSortedMap() 方法
如果需要线程安全的排序 Map,可以使用 synchronizedSortedMap()
方法,它返回一个由 SortedMap
实现支持的同步视图:
Map<Integer, String> syncSortedMap = Collections.synchronizedSortedMap(new TreeMap<>());
6. synchronizedSet() 方法
同样的,使用 synchronizedSet()
方法可以轻松创建线程安全的 Set
:
Set<Integer> syncSet = Collections.synchronizedSet(new HashSet<>());
7. synchronizedSortedSet() 方法
最后是 synchronizedSortedSet()
方法,用于创建线程安全的排序 Set:
SortedSet<Integer> syncSortedSet = Collections.synchronizedSortedSet(new TreeSet<>());
8. 同步集合 vs 并发集合
前面我们介绍了 Collections
提供的同步包装器,现在我们来聊聊它们与 Java 并发包中的并发集合(如 ConcurrentHashMap
和 BlockingQueue
)有什么不同。
8.1. 同步集合
同步集合通过内置锁(intrinsic locking)实现线程安全。即每个方法都通过 synchronized
关键字加锁,整个集合对象被锁定。
✅ 优点:保证了数据一致性。
❌ 缺点:性能较差,同一时间只能有一个线程访问集合(串行化访问)。
8.2. 并发集合
并发集合(如 ConcurrentHashMap
)通过将数据结构分段(segmentation)来实现线程安全。
✅ 优点:多个线程可以同时访问不同段,性能更高。
❌ 缺点:不提供强一致性语义,但在大多数场景下已经足够。
💡 建议:对于高并发读写场景,优先选择并发集合;如果并发度不高,同步集合也完全够用。
9. 总结
本文深入讲解了 Java Collections
类中提供的同步集合包装器,包括 synchronizedList
、synchronizedMap
等方法的使用方式。
同时,我们也对比了同步集合与并发集合在实现机制和性能表现上的差异,帮助你在实际开发中做出更合适的选择。
一如既往,本文所有示例代码均可在 GitHub 上找到。