Java 性能调优实战
刘超
前金山软件技术经理
59174 人已学习
新⼈⾸单¥59
登录后,你可以任选4讲全文学习
课程目录
已完结/共 49 讲
开篇词 (1讲)
模块一 · 概述 (2讲)
结束语 (1讲)
Java 性能调优实战
15
15
1.0x
00:00/00:00
登录|注册

17 | 并发容器的使用:识别不同场景下最优容器

读操作无锁,写操作通过操作底层数组的新副本实现
基于Synchronized同步锁实现的线程安全
非线程安全,不适合并发场景
适用场景:数据量在千万级别,存在大量增删改操作
适用场景:大部分场景通常都是弱一致性的情况下
适用场景:数据有强一致要求
CopyOnWriteArrayList
Vector
ArrayList
ConcurrentSkipListMap
ConcurrentHashMap
Hashtable
并发场景下的List容器
并发场景下的Map容器
快速识别不同场景下的最佳并发容器
参考文章

该思维导图由 AI 生成,仅供参考

你好,我是刘超。
在并发编程中,我们经常会用到容器。今天我要和你分享的话题就是:在不同场景下我们该如何选择最优容器。

并发场景下的 Map 容器

假设我们现在要给一个电商系统设计一个简单的统计商品销量 TOP 10 的功能。常规情况下,我们是用一个哈希表来存储商品和销量键值对,然后使用排序获得销量前十的商品。在这里,哈希表是实现该功能的关键。那么请思考一下,如果要你设计这个功能,你会使用哪个容器呢?
在 07 讲中,我曾详细讲过 HashMap 的实现原理,以及 HashMap 结构的各个优化细节。我说过 HashMap 的性能优越,经常被用来存储键值对。那么这里我们可以使用 HashMap 吗?
答案是不可以,我们切忌在并发场景下使用 HashMap。因为在 JDK1.7 之前,在并发场景下使用 HashMap 会出现死循环,从而导致 CPU 使用率居高不下,而扩容是导致死循环的主要原因。虽然 Java 在 JDK1.8 中修复了 HashMap 扩容导致的死循环问题,但在高并发场景下,依然会有数据丢失以及不准确的情况出现。
这时为了保证容器的线程安全,Java 实现了 Hashtable、ConcurrentHashMap 以及 ConcurrentSkipListMap 等 Map 容器。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入探讨了在不同并发场景下选择最佳并发容器的方法。作者首先讨论了在并发场景下的Map容器的选择,针对电商系统的商品销量TOP 10功能,提出了针对小数据量和大数据量存取的不同建议。其次,文章讨论了在并发场景下的List容器的选择,以电商系统中的用户黑名单为例,提出了使用CopyOnWriteArrayList来实现读远大于写的操作业务。通过具体案例和性能分析,本文为读者提供了实用的技术指导,指导读者在不同并发场景下选择最适合的并发容器。文章内容丰富,对于需要在并发编程中选择合适容器的读者来说,是一篇值得深入阅读的文章。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《Java 性能调优实战》
新⼈⾸单¥59
立即购买
登录 后留言

全部留言(49)

  • 最新
  • 精选
  • 晓杰
    可以用ConcurrentLinkedQueue,优势如下: 1、抢购场景一般都是写多读少,该队列基于链表实现,所以新增和删除元素性能较高 2、写数据时通过cas操作,性能较高。 但是LinkedQueue有一个普遍存在的问题,就是该队列是无界的,需要控制容量,否则可能引起内存溢出

    作者回复: 对的

    2019-06-28
    3
    46
  • 东方奇骥
    如果数据变动频繁,就不建议使用CopyOnWriteArrayList了,因为每次写都要拷贝一份,代价太大。老师,怎么直观理解强一致性和弱一致性?之前一直觉得ConcurrentHashMap就是用来代替HashTable的,因为HashTable并发时因为同步锁性能差。

    作者回复: 对的,CopyOnWriteArrayList只适合偶尔一两次数据更改的操作。我们很多缓存数据往往是在深夜在没有读操作时,进行修改。这种场景适合使用CopyOnWriteArrayList。 我们先来理解下happens-before规则中,对锁的规则: 一个unLock操作先行发生于后面对同一个锁的lock操作; 也就是说,ConcurrentHashMap中的get如果有锁操作,在put操作之后,get操作是一定能拿到put后的数据;而实际上get操作时没有锁的,也就是说下面这种情况: void func(){ map.put(key1,value1); map.get(key1); . . //use key1 value to do something } 此时,get获取值的可能不是put修改的值,而此时get没有获取到真正要获取的值,此时就是弱一致了。

    2019-06-27
    7
    28
  • 学无涯
    为什么ConcurrentHashMap和HashTable的key和value不能为空,而HashMap却可以,这么设计的原因是什么呢

    作者回复: 这跟并发有关系,我们知道ConcurrentHashMap和HashTable都是线程安全的,假设允许key和value为null,有以下代码: if (map.containsKey(key)) {//代码1 return map.get(key);//代码2 } else { throw new KeyNotPresentException(); } 当在并发情况下,有两个线程分别在操作map容器,此时线程1在运行以上代码,当线程1运行到代码1与代码2中间时,刚好有另外一个线程2执行了map.remove(key)操作,此时继续运行代码2时,依然会返回null值。而此时的null实际上是map中真实的不存在该key值,应该throw new KeyNotPresentException()的。所以为了保证线程安全,这两个Map容器是不允许key和value为null。 而HashMap是非线程安全的,不存在以上我们所说的并发情况。

    2019-11-12
    7
    25
  • undifined
    抢购的过程中存在并发操作,所以需要用线程安全的容器,同时,抢购的用户会很多,应当使用链表的数据结构,这种场景往往是写多读少,还需要排队,所以 ConcurrentLinkedQueue应该是最合适的

    作者回复: 对的,ConcurrentLinkedQueue是基于CAS乐观锁来实现线程安全。ConcurrentLinkedQueue是无界的,所以使用的时候要特别注意内存溢出问题。

    2019-06-27
    13
  • 大雁小鱼
    老师,ConcurrentHashMap为啥是弱一致性的?

    作者回复: 因为ConcurrentHashMap有些方法是没有锁的,例如get 方法。假设A修改了数据,而B后于A一瞬间去获取数据,有可能拿到的数据是A修改之前的数据。 还有 clear foreach方法在操作时,都有可能存在数据不确定性。

    2019-06-27
    7
  • 天天向上
    ConcurrentHashMap 是弱一致性的,因此有可能会导致某次读无法马上获取到写入的数据。这句话不理解啊,为什么会无法马上获取到写入的数据呢?又不像mysql那样存在事务隔离。

    作者回复: 虽然Node<k,v>和value被volatile修饰,可以获取到最新值,如果是一个新Node,那么就不能马上在table中看到。虽然Node的数组table被volatile修饰,但是这样只是代表table的引用地址被修改,其他线程可以立马看到,并不代表table里的数据被修改立马可以看到。

    2020-04-26
    6
  • 陆离
    这一节要是有Queue就更好了,那几个blockingQueue还是很有意思的

    编辑回复: 多留言哦~说不定在下一期的加餐中就惊现福利了!😎ི

    2019-06-27
    6
  • WL
    请问一下老师两个问题: 1. 为什么在无锁是红黑树和跳表的性能差不多呢, 红黑树再平衡的操作会不会更复杂一些. 2. 从本篇文章看好像ConcurrentSkipListMap的性能比ConcurrentHashMap性能要好, 那为啥平时还是用后者的人更多呢, 我想很定是后者相对前者也有一定的优势吧, 但我自己没想出来, 老师能不能指点一下是啥优势.

    作者回复: 1、两者的平均查询复杂度都是O(logn),所以查询性能差不多。而在新增和删除操作,红黑树有平衡操作,但跳跃表也有建立索引层操作。跳跃表的结构简单易懂。 2、这里是基于数据量比较大(例如千万级别)且写入操作多的情况下,ConcurrentSkipListMap性能要比ConcurrentHashMap好一些,并不是在任何情况下都要优于ConcurrentHashMap的。

    2019-06-27
    3
    5
  • Liam
    老师,我有2个问题: 1 top 10 问题涉及到排序, 我感觉用优先级队列或带排序功能的ConcurrentSkipListMap更合适?ConcurrentHashMap不支持排序吧 2 CopyOnWrite的list为什么还要加锁呢,副本不是线程独享的吗?

    作者回复: ConcurrentSkipListMap只是key值的升排序,并没有对value进行排序; CopyOnWrite在副本写时,是需要加锁的。

    2019-06-27
    3
  • 学无涯
    老师,我的意思是Unsafe#compareAndSetObject和Unsafe#getObjectVolatile方法,这两个方法的volatile语义可以保证数组元素的可见性。这样即使新增一个node,这俩方法也可以保证其他线程可以读到。还是说我对这两个方法有误解,请老师解惑!

    作者回复: 如果是一个新Node,那么就不能马上看到,虽然Node的数组table被volatile修饰,但是这样只是代表table的引用地址如果被修改,其他线程可以立马看到,并不代表table里的数据被修改立马可以看到。

    2019-11-13
    2
    2
收起评论
显示
设置
留言
49
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部