这些并发容器的坑,你要谨记!(代码片段)

华为云开发者联盟 华为云开发者联盟     2022-10-22     343

关键词:

摘要:JDK1.5及之后的版本中,提供的线程安全的容器,一般被称为并发容器。与同步容器一样,并发容器在总体上也可以分为四大类,分别为:List、Set、Map和Queue。

本文分享自华为云社区《【高并发】要想学好并发编程,这些并发容器的坑是你必须要注意的!!(建议收藏)》,作者:冰 河 。

其实,在JDK1.5之前的线程安全的容器,大多数都是指同步容器,使用同步容器进行并发编程时,最大的问题就是性能很差。因为同步容器中的所有方法都是使用synchronized锁进行互斥,串行度太高了,无法真正的做到并行。

所以,在JDK1.5之后,JDK中提供了并发性能更好的容器。JDK1.5及之后的版本中,提供的线程安全的容器,一般被称为并发容器。

并发容器

与同步容器一样,并发容器在总体上也可以分为四大类,分别为:List、Set、Map和Queue。总体上如下图所示。

接下来,我们分别介绍下这些并发容器在使用时的注意事项和避免踩到的坑。

List

并发容器中的List相对来说比较简单,就一个CopyOnWriteArrayList。大家可以从字面的意思中就能够体会到:CopyOnWrite,在写的时候进行复制操作,也就是说在进行写操作时,会将共享变量复制一份。那这样做有什么好处呢?最大的好处就是:读操作可以做到完全无锁化

在CopyOnWriteArrayList内部维护了一个数组,成员变量array指向这个数组,其核心源代码如下所示。

private transient volatile Object[] array;
final Object[] getArray() 
return array;

final void setArray(Object[] a) 
array = a;

当进行操作时,都是基于array指向的这个内部数组进行的。例如,我们使用Iterator迭代器遍历这个数组时,会按照下图所示的方式进行读操作。

如果在遍历CopyOnWriteArrayList时发生写操作,例如,向数组中增加一个元素时,CopyOnWriteArrayList则会将内部的数组复制一份出来,然后会在新复制出来的数组上添加新的元素,添加完再将array指向新的数组,如下图所示。

对于CopyOnWriteArrayList的其他写操作和添加元素的操作原理相同,这里就不再赘述了。

使用CopyOnWriteArrayList时需要注意的是:

  • CopyOnWriteArrayList只适合写操作比较少的场景,并且能够容忍读写操作在短时间内的不一致。
  • CopyOnWriteArrayList的迭代器是只读的,不支持写操作。

Set

对于Set接口来说,并发容器中主要有两个实现类,一个是CopyOnWriteArraySet,另一个是ConcurrentSkipListSet。其中,CopyOnWriteArraySet的使用场景、原理与注意事项和CopyOnWriteArrayList一致。而ConcurrentSkipListSet的使用场景、原理和注意事项和下文的ConcurrentSkipListMap一致。这里,我就不再赘述啦。

Map

在并发容器中,Map接口的实现类主要有ConcurrentHashMap和ConcurrentSkipListMap,而ConcurrentHashMap和ConcurrentSkipListMap最大的区别就是:ConcurrentHashMap的Key是无序的,而ConcurrentSkipListMap的Key是有序的。

在使用ConcurrentHashMap和ConcurrentSkipListMap时,需要注意的是:ConcurrentHashMap和ConcurrentSkipListMap的Key和Value都不能为空。

这里,我们可以将Map相关的类总结成一个表格,如下所示。

这样,大家记忆起来就方便多了。

这里,ConcurrentSkipListMap是基于“跳表”实现的,跳表的插入、删除、查询的平均时间复杂度为O(log n),这些时间复杂度在理论上与线程数没有关系。如果要追求性能的话,可以尝试使用ConcurrentSkipListMap。

Queue

在Java的并发容器中,Queue相对来说比较复杂。我们先来了解几个概念:

  • 阻塞队列:阻塞一般就是指当队列已满时,入队操作会阻塞;当队列为空时,出队操作就会阻塞。
  • 非阻塞队列:队列的入队和出队操作不会阻塞。
  • 单端队列:队列的入队操作只能在队尾进行,队列的出队操作只能在队首进行。
  • 双端队列:队列的入队操作和出队操作都可以在队首和队尾进行。

我们可以将上述的队列进行组合,将队列分为单端阻塞队列、双端阻塞队列、单端非阻塞队列和双端非阻塞队列。

在Java的并发容器中,会使用明显的标识来区分不同类型的队列。

  • 阻塞队列一个明显的标识就是使用Blocking修饰,例如,ArrayBlockingQueue和LinkedBlockingQueue都是阻塞队列。
  • 单端队列会使用Queue标识,例如ArrayBlockingQueue和LinkedBlockingQueue也是单端队列。
  • 双端队列会使用Deque标识,例如LinkedBlockingDeque和ConcurrentLinkedDeque都是双端队列。

接下来,我们就分别简单聊聊这四种类型的队列。

单端阻塞队列

在Java的并发容器中,单端阻塞队列的主要实现是BlockingQueue,主要包括:ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue、LinkedTransferQueue、PriorityBlockingQueue和DelayQueue。

单端阻塞队列的内部一般会有一个队列。

在实现上,内部的队列可以是数组,例如ArrayBlockingQueue,也可以是链表,例如LinkedBlockingQueue。

也可以在内部不存在队列,例如SynchronousQueue,SynchronousQueue实现了生产者的入队操作必须等待消费者的出队操作完成之后才能进行。

LinkedTransferQueue集成了LinkedBlockingQueue和SynchronousQueue的优点,并且性能比LinkedBlockingQueue好。

PriorityBlockingQueue实现了按照优先级进行出队操作,也就是说,队列元素在PriorityBlockingQueue内部可以按照某种规则进行排序。

DelayQueue是延时队列,实现了在一段时间后再出队的操作。

双端阻塞队列

双端阻塞队列的实现主要是LinkedBlockingDeque。示意图如下所示。

单端非阻塞队列

单端非阻塞队列的实现主要是ConcurrentLinkedQueue,示意图如下所示。

双端非阻塞队列

双端非阻塞队列的实现主要是ConcurrentLinkedDeque,示意图如下所示。

有界与无界队列

使用队列时,还要注意队列的有界与无界问题,也就是在使用队列时,需要注意队列是否有容量限制

在实际工作中,一般推荐使用有界队列。因为无界队列很容易导致内存溢出的问题。在Java的并发容器中,只有ArrayBlockingQueue和LinkedBlockingQueue支持有界,其他的队列都是无界队列。

在使用时,一定要注意内存溢出问题。

总结

今天我们主要介绍了JDK1.5之后提供的并发容器,主要包括:List、Set、Map和Queue,而Queue又可以分为:单端阻塞队列、双端阻塞队列、单端非阻塞队列和双端非阻塞队列。对于每种并发容器,我们简单介绍了其基本原理和注意事项。

点击关注,第一时间了解华为云新鲜技术~

奉劝那些刚参加工作的学弟学妹们:要想学好并发编程,这些并发容器的坑是你必须要注意的!!(建议收藏)(代码片段)

大家好,我是冰河~~在上一篇《亿级流量高并发秒杀系统商品“超卖”了,只因使用的JDK同步容器中存在这两个巨大的坑!!(踩坑实录,建议收藏)》中,我们主要一起学习了JDK中同步容器的两个... 查看详情

奉劝那些刚参加工作的学弟学妹们:要想学好并发编程,这些并发容器的坑是你必须要注意的!!(建议收藏)(代码片段)

大家好,我是冰河~~在上一篇《亿级流量高并发秒杀系统商品“超卖”了,只因使用的JDK同步容器中存在这两个巨大的坑!!(踩坑实录,建议收藏)》中,我们主要一起学习了JDK中同步容器的两个... 查看详情

亿级流量高并发秒杀系统商品“超卖”了,只因使用的jdk同步容器中存在这两个巨大的坑!!(踩坑实录,建议收藏)(代码片段)

...器时,确实踩过不少坑。为了让小伙伴们更好的消化这些知识,今天,首先和小伙伴们分享下使用同步容器时需要注意哪些问题,后续再为大家分享使用并发容器时需要注意哪些 查看详情

亿级流量高并发秒杀系统商品“超卖”了,只因使用的jdk同步容器中存在这两个巨大的坑!!(踩坑实录,建议收藏)(代码片段)

...器时,确实踩过不少坑。为了让小伙伴们更好的消化这些知识,今天,首先和小伙伴们分享下使用同步容器时需要注意哪些问题,后续再为大家分享使用并发容器时需要注意哪些 查看详情

多线程5-同步容器和并发容器(代码片段)

...身是一个顶层接口。我们常用的ArrayList、LinkedList、HashMap这些容器都是非线程安全的,如果有多个线程并发访问这些容器时,就会出现问题。因此,编写程序时,必须要求开发者手动在任何访问到这些容器的地 查看详情

jdk并发容器(代码片段)

JDK提供的并发容器总结JDK提供的这些容器大部分在 java.util.concurrent 包中。ConcurrentHashMap: 线程安全的HashMapCopyOnWriteArrayList: 线程安全的List,在读多写少的场合性能非常好,远远好于Vector.ConcurrentLinkedQueue: 高效的... 查看详情

java并发编程学习6-同步容器类和并发容器(代码片段)

...类库下的一些最常用的并发基础构建模块,以及使用这些模块来构造并发应用程序时的一些常用模式。同步容器类同步容器类包括Vector和Hashtable,还有由Collections.synchronizedXxx等工厂方法创建的同步的封装器类。这些类实... 查看详情

java并发编程学习6-同步工具类和并发容器(代码片段)

...类库下的一些最常用的并发基础构建模块,以及使用这些模块来构造并发应用程序时的一些常用模式。同步容器类同步容器类包括Vector和Hashtable,还有由Collections.synchronizedXxx等工厂方法创建的同步的封装器类。这些类实... 查看详情

使用jdk的同步容器时,应该避免那些坑?(代码片段)

...,应该尽量避免哪些坑本文分享自华为云社区《【高并发】亿级流量高并发秒杀系统商品“超卖”了,只因使用的JDK同步容器中存在这两个巨大的坑!!(踩坑实录)》,作者:冰 河。同步容器与并发... 查看详情

同步类容器并发修改的问题(代码片段)

...找到当前元素的下一个元素)、以及条件运算等。3、在这些复合操作下,进行并发的修改(add或remove)容器时,会抛出java.util.ConcurrentModificationException异常。在早期的迭代器设计的时候并没有考虑并发修改的 查看详情

并发编程-线程安全策略之两种类型的同步容器(代码片段)

...ayList、HashMap、HashSet),如果有多个线程并发访问这些集合时就会出现线程不安全的问题。当我们在使用这些容器时,需要我们自己来处理线程安全的问题。使用起来相对会有些不便,而Java在这方面提供了相应的... 查看详情

并发容器非阻塞队列的并发容器(代码片段)

??本文将介绍除了阻塞队列外的并发容器:ConcurrentHashMap、CopyOnWriteArrayList、CopyOnWriteArraySet、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue;1.CopyOnWriteArrayList是ArrayList的线程安全的实现,同时也可用于代替Vector 查看详情

记uwp开发——多线程操作/并发操作中的坑(代码片段)

一切都要从新版风车动漫UWP的图片缓存功能说起。 起因便是风车动漫官网的番剧更新都很慢,所以图片更新也非常慢。在开发新版的过程中,我很简单就想到了图片多次重复下载导致的资源浪费问题。所以我给app加了一个缓... 查看详情

java常见并发容器总结(代码片段)

Java常见并发容器总结ConcurrentHashMapCopyOnWriteArrayListCopyOnWriteArrayList简介CopyOnWriteArrayList是如何做到的?CopyOnWriteArrayList读取和写入源码简单分析CopyOnWriteArrayList读取操作的实现CopyOnWriteArrayList写入操作的实现Concurrent 查看详情

day839.并发容器-java并发编程实战(代码片段)

并发容器Hi,我是阿昌,今天学习记录的是关于并发容器。Java1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差。Java1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富... 查看详情

day839.并发容器-java并发编程实战(代码片段)

并发容器Hi,我是阿昌,今天学习记录的是关于并发容器。Java1.5之前提供的同步容器虽然也能保证线程安全,但是性能很差。Java1.5版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富... 查看详情

并发编程-线程安全策略之并发容器(j.u.c)中的集合类(代码片段)

J.U.C总览脑图 同步容器: 并发容器 概述同步容器是通过synchronized来实现同步的,所以性能较差。而且同步容器也并不是绝对线程安全的,在一些特殊情况下也会出现线程不安全的行为。那么有没有更好的方式代替... 查看详情

19.并发容器之blockingqueue(代码片段)

...架中的各种容器类如实现List,Map,Queue接口的容器类,但是这些容器类基本上不是线程安全的,除了使用Collections可以将其转换为线程安全的容器,DougLea大师为我们都准备了对应的线程安全的容器,如实现List接口的CopyOnWriteArrayList... 查看详情