写点什么

探索 Java 并发容器的深坑与妙用:从同步到并发的进化之路

作者:刘祥
  • 2024-06-29
    陕西
  • 本文字数:1950 字

    阅读完需:约 6 分钟

探索 Java 并发容器的深坑与妙用:从同步到并发的进化之路

在 Java 的世界里,并发编程一直是一个复杂而又充满挑战的领域。尤其是在处理并发容器时,不同版本、不同类型的容器带来的种种问题和优化,更是让人眼花缭乱。今天,我们将深入探讨 Java 并发容器的演化历程,揭示其中的深坑与妙用,帮助你在实际工作中更好地选择和使用这些容器。

从同步容器到并发容器:性能与安全的权衡

同步容器的前世今生

在 Java 1.5 之前,线程安全的容器主要依赖同步容器(Synchronized Collections)。这些容器通过synchronized关键字实现线程安全,但也因此带来了显著的性能瓶颈。下面是一个将非线程安全的ArrayList封装成线程安全容器的示例:


class SafeArrayList<T> {  private final List<T> list = new ArrayList<>();
synchronized T get(int index) { return list.get(index); }
synchronized void add(int index, T element) { list.add(index, element); }
synchronized boolean addIfNotExist(T element) { if (!list.contains(element)) { list.add(element); return true; } return false; }}
复制代码


虽然这种封装方式可以保证线程安全,但组合操作(如addIfNotExist)仍然存在竞态条件,无法保证操作的原子性。此外,遍历操作也需要显式加锁,否则会引发并发问题:


List<String> list = Collections.synchronizedList(new ArrayList<>());synchronized (list) {  Iterator<String> iterator = list.iterator();  while (iterator.hasNext()) {    System.out.println(iterator.next());  }}
复制代码

并发容器的崛起

为了提升性能,Java 在 1.5 版本引入了并发容器(Concurrent Collections),这些容器通过更细粒度的锁机制或无锁算法实现线程安全,极大地提高了并发性能。

深入探讨并发容器:特性与注意事项

1. List:CopyOnWriteArrayList

CopyOnWriteArrayList采用写时复制(Copy-On-Write)机制,读操作无锁,写操作则在新数组上进行,写完后再替换原数组。这种机制使得读操作非常高效,但写操作代价较高,适用于读多写少的场景。


CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();list.add("Hello");list.add("World");
复制代码


需要注意的是,CopyOnWriteArrayList的迭代器是只读的,不支持增删改操作。

2. Map:ConcurrentHashMap 与 ConcurrentSkipListMap

ConcurrentHashMap通过分段锁机制实现高并发性能,每个分段独立加锁,减少了锁竞争。ConcurrentSkipListMap则基于跳表(Skip List)实现,支持有序的键值对存储。


ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();map.put("key1", "value1");map.put("key2", "value2");
ConcurrentSkipListMap<String, String> skipListMap = new ConcurrentSkipListMap<>();skipListMap.put("key1", "value1");skipListMap.put("key2", "value2");
复制代码


需要注意的是,这些并发 Map 不允许键或值为 null,否则会抛出NullPointerException

3. Set:CopyOnWriteArraySet 与 ConcurrentSkipListSet

CopyOnWriteArraySetConcurrentSkipListSet分别基于CopyOnWriteArrayListConcurrentSkipListMap实现,适用于不同的使用场景。

4. Queue:从阻塞到非阻塞

Java 并发包中的 Queue 种类繁多,可以从阻塞与非阻塞、单端与双端两个维度进行分类。

单端阻塞队列

包括ArrayBlockingQueueLinkedBlockingQueueSynchronousQueueLinkedTransferQueuePriorityBlockingQueueDelayQueue。这些队列适用于不同的并发场景,例如SynchronousQueue适合生产者-消费者模式。

双端阻塞队列

LinkedBlockingDeque是双端阻塞队列的代表,支持在队首和队尾进行插入和删除操作。

非阻塞队列

包括ConcurrentLinkedQueueConcurrentLinkedDeque,适用于高并发、低延迟的场景。

有界与无界队列

在实际工作中,建议优先使用有界队列(如ArrayBlockingQueueLinkedBlockingQueue),以防止数据量过大导致内存溢出(OOM)。

实践中的选择与应用

在选择并发容器时,了解每种容器的特性和适用场景至关重要。以下是一些常见的选择指南:


  • 读多写少的场景:选择CopyOnWriteArrayListCopyOnWriteArraySet

  • 需要有序键值对的 Map:选择ConcurrentSkipListMap

  • 高并发、低延迟的场景:选择ConcurrentLinkedQueueConcurrentLinkedDeque

  • 生产者-消费者模式:选择SynchronousQueueLinkedTransferQueue

总结

Java 并发容器为我们提供了丰富的选择,但每种容器都有其适用的场景和注意事项。在实际工作中,理解这些容器的特性,选对容器,才能真正发挥它们的优势,写出高效、安全的并发程序。


希望这篇文章能帮助你在并发编程的道路上走得更远,少踩坑,多收获。如果你有任何疑问或经验分享,欢迎在评论区留言讨论。让我们一起探索 Java 并发编程的奥秘!

用户头像

刘祥

关注

个人公众号|码上代码 2020-03-06 加入

码上代码 |CSDNjava领域优质创作者分享

评论

发布
暂无评论
探索Java并发容器的深坑与妙用:从同步到并发的进化之路_并发_刘祥_InfoQ写作社区