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

奉劝那些刚参加工作的学弟学妹们:要想学好并发编程,这些并发容器的坑是你必须要注意的!!(建议收藏)「建议收藏」冰河总结了在高并发、大流量场景下使用JDK中并发容器的坑,要想学好并发编程,这些坑是你一定要注意的,冰河强烈建议收藏!!

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

大家好,我是冰河~~

在上一篇《亿级流量高并发秒杀系统商品“超卖”了,只因使用的JDK同步容器中存在这两个巨大的坑!!(踩坑实录,建议收藏)》中,我们主要一起学习了JDK中同步容器的两个坑,一个是在使用同步容器时可能会出现 竞态条件 的问题,一个是在使用同步容器时用 迭代器遍历容器 可能会踩坑。

如果文章对你有点帮助,小伙伴们点赞,收藏,评论,分享,走起呀~~

其实,在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相关的类总结成一个表格,如下所示。

Map的实现类 Key是否可为空 Value是否可为空 是否是线程安全的
HashMap
TreeMap
HashTable
ConcurrentHashMap
ConcurrentSkipListMap

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

这里,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又可以分为:单端阻塞队列、双端阻塞队列、单端非阻塞队列和双端非阻塞队列。对于每种并发容器,我们简单介绍了其基本原理和注意事项。

写在最后

如果你想进大厂,想升职加薪,或者对自己现有的工作比较迷茫,都可以私信我交流,希望我的一些经历能够帮助到大家~~

推荐阅读:

好了,今天就到这儿吧,小伙伴们点赞、收藏、评论,一键三连走起呀,我是冰河,我们下期见~~

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/172043.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • docker(10)上传本地镜像到镜像仓库[通俗易懂]

    docker(10)上传本地镜像到镜像仓库[通俗易懂]前言之前通过docker搭建过jenkins+python3环境,如果想要在不同的机器上搭建一样的环境,就可以将之前搭建的镜像上传到镜像仓库,这样方便在不同的机器上快速搭建同一套环境。如果公开的话

    2022年7月30日
    8
  • 动态规划经典题目_动态规划详解

    动态规划经典题目_动态规划详解一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?示例 1:输入:m = 3, n = 7输出:28示例 2:输入:m = 3, n = 2输出:3解释:从左上角开始,总共有 3 条路径可以到达右下角。向右 -> 向下 -> 向下向下 -> 向下 -> 向右向下 -> 向右 -&gt

    2022年8月9日
    3
  • linux 查看java的pid,linux 查看java进程pid「建议收藏」

    linux 查看java的pid,linux 查看java进程pid「建议收藏」linux查看java进程pid[2021-01-3021:05:24]简介:建站服务器这篇文章主要介绍了linux中如何查看系统进程,具有一定借鉴价值,需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获。下linux查看端口被哪个进程占用的方法:1、使用“lsof-i:端口号”来查看;2、使用“netstat-tunlp|grep端口号”来查看。linux查看端口被哪个进程占…

    2022年8月24日
    7
  • libxml2 c教程_linux怎么用gcc编译

    libxml2 c教程_linux怎么用gcc编译我的开发环境是ubuntu18嵌入式的环境也是ubuntu,只需要执行下面的动作即可虚拟机上执行,设备上也执行apt-getinstalllibxml2apt-getinstalllibxml2-dev虚拟机上建立连接sudoln-s/usr/include/libxml2/libxml/usr/include/libxml开发的时候引入”x…

    2025年5月24日
    0
  • 安卓错误总结

    安卓错误总结

    2022年2月7日
    42
  • 未连接到互联网代理服务器出现问题或地址有误(代理服务器的ip地址是多少)

    今天遇到一个问题:【校园网】打开电脑其实网络无法连接到安全代理服务器,本地IP地址非法,无法打开本地连接属性既看不到TCP/IP地址框。解决方法:1.重新启动计算机后发现网卡被禁用,重新启用就好了。

    2022年4月13日
    378

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号