Java性能调优实战
刘超
金山软件西山居技术经理
立即订阅
7535 人已学习
课程目录
已完结 48 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 怎样才能做好性能调优?
免费
模块一 · 概述 (2讲)
01 | 如何制定性能调优标准?
02 | 如何制定性能调优策略?
模块二 · Java编程性能调优 (10讲)
03 | 字符串性能优化不容小觑,百M内存轻松存储几十G数据
04 | 慎重使用正则表达式
05 | ArrayList还是LinkedList?使用不当性能差千倍
加餐 | 推荐几款常用的性能测试工具
06 | Stream如何提高遍历集合效率?
07 | 深入浅出HashMap的设计与优化
08 | 网络通信优化之I/O模型:如何解决高并发下I/O瓶颈?
09 | 网络通信优化之序列化:避免使用Java序列化
10 | 网络通信优化之通信协议:如何优化RPC网络通信?
11 | 答疑课堂:深入了解NIO的优化实现原理
模块三 · 多线程性能调优 (10讲)
12 | 多线程之锁优化(上):深入了解Synchronized同步锁的优化方法
13 | 多线程之锁优化(中):深入了解Lock同步锁的优化方法
14 | 多线程之锁优化(下):使用乐观锁优化并行操作
15 | 多线程调优(上):哪些操作导致了上下文切换?
16 | 多线程调优(下):如何优化多线程上下文切换?
17 | 并发容器的使用:识别不同场景下最优容器
18 | 如何设置线程池大小?
19 | 如何用协程来优化多线程业务?
20 | 答疑课堂:模块三热点问题解答
加餐 | 什么是数据的强、弱一致性?
模块四 · JVM性能监测及调优 (6讲)
21 | 磨刀不误砍柴工:欲知JVM调优先了解JVM内存模型
22 | 深入JVM即时编译器JIT,优化Java编译
23 | 如何优化垃圾回收机制?
24 | 如何优化JVM内存分配?
25 | 内存持续上升,我该如何排查问题?
26 | 答疑课堂:模块四热点问题解答
模块五 · 设计模式调优 (6讲)
27 | 单例模式:如何创建单一对象优化系统性能?
28 | 原型模式与享元模式:提升系统性能的利器
29 | 如何使用设计模式优化并发编程?
30 | 生产者消费者模式:电商库存设计优化
31 | 装饰器模式:如何优化电商系统中复杂的商品价格策略?
32 | 答疑课堂:模块五思考题集锦
模块六 · 数据库性能调优 (8讲)
33 | MySQL调优之SQL语句:如何写出高性能SQL语句?
34 | MySQL调优之事务:高并发场景下的数据库事务调优
35 | MySQL调优之索引:索引的失效与优化
36 | 记一次线上SQL死锁事故:如何避免死锁?
37 | 什么时候需要分表分库?
38 | 电商系统表设计优化案例分析
39 | 数据库参数设置优化,失之毫厘差之千里
40 | 答疑课堂:MySQL中InnoDB的知识点串讲
模块七 · 实战演练场 (4讲)
41 | 如何设计更优的分布式锁?
42 | 电商系统的分布式事务调优
43 | 如何使用缓存优化系统性能?
44 | 记一次双十一抢购性能瓶颈调优
结束语 (1讲)
结束语 | 栉风沐雨,砥砺前行!
Java性能调优实战
登录|注册

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

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

并发场景下的 Map 容器

假设我们现在要给一个电商系统设计一个简单的统计商品销量 TOP 10 的功能。常规情况下,我们是用一个哈希表来存储商品和销量键值对,然后使用排序获得销量前十的商品。在这里,哈希表是实现该功能的关键。那么请思考一下,如果要你设计这个功能,你会使用哪个容器呢?
在 07 讲中,我曾详细讲过 HashMap 的实现原理,以及 HashMap 结构的各个优化细节。我说过 HashMap 的性能优越,经常被用来存储键值对。那么这里我们可以使用 HashMap 吗?
答案是不可以,我们切忌在并发场景下使用 HashMap。因为在 JDK1.7 之前,在并发场景下使用 HashMap 会出现死循环,从而导致 CPU 使用率居高不下,而扩容是导致死循环的主要原因。虽然 Java 在 JDK1.8 中修复了 HashMap 扩容导致的死循环问题,但在高并发场景下,依然会有数据丢失以及不准确的情况出现。
这时为了保证容器的线程安全,Java 实现了 Hashtable、ConcurrentHashMap 以及 ConcurrentSkipListMap 等 Map 容器。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java性能调优实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(39)

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

    作者回复: 对的

    2019-06-28
    12
  • 东方奇骥
    如果数据变动频繁,就不建议使用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
    2
    9
  • 大雁小鱼
    老师,ConcurrentHashMap为啥是弱一致性的?

    作者回复: 因为ConcurrentHashMap有些方法是没有锁的,例如get 方法。假设A修改了数据,而B后于A一瞬间去获取数据,有可能拿到的数据是A修改之前的数据。

    还有 clear foreach方法在操作时,都有可能存在数据不确定性。

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

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

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

    作者回复: 1、两者的平均查询复杂度都是O(logn),所以查询性能差不多。而在新增和删除操作,红黑树有平衡操作,但跳跃表也有建立索引层操作。跳跃表的结构简单易懂。

    2、这里是基于数据量比较大(例如千万级别)且写入操作多的情况下,ConcurrentSkipListMap性能要比ConcurrentHashMap好一些,并不是在任何情况下都要优于ConcurrentHashMap的。

    2019-06-27
    1
    3
  • WL
    赞成讲下那几个blockingQueue
    2019-06-29
    2
  • 听雨
    为什么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
    1
  • godtrue
    还不错,今天认识到ConcurrentHashMap存在数据弱一致性问题,发生弱一致性问题应该是个小概率事件吧!老师清楚大概什么数据量什么概率嘛?
    老师讲解的不全呀😄JUC中并发容器可多了,最典型的都没讲全,建议列个全一些的对照表更好一些。
    不过,他们的特点其实和非多线程安全的主要就差在是否安全,然后就是自身结构决定的一些特性啦!
    比如:读写性能、是否有界、是否阻塞、MAP存储键值对、LIST通常适合写多的场景、QUEUE适合排队等待等等。
    2019-09-10
    1
  • 陆离
    这一节要是有Queue就更好了,那几个blockingQueue还是很有意思的

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

    2019-06-27
    1
  • Liam
    老师,我有2个问题:

    1 top 10 问题涉及到排序, 我感觉用优先级队列或带排序功能的ConcurrentSkipListMap更合适?ConcurrentHashMap不支持排序吧

    2 CopyOnWrite的list为什么还要加锁呢,副本不是线程独享的吗?

    作者回复: ConcurrentSkipListMap只是key值的升排序,并没有对value进行排序;

    CopyOnWrite在副本写时,是需要加锁的。

    2019-06-27
    1
  • 张学磊
    个人认为抢购的排队等待应该使用ConcurrentLinkedQueue,相比于ArrayBlockingQueue(一把全局锁)和LinkedBlockingQueue(存取采用两把锁),CLQ是无锁的,使用CAS操作,不存在锁的争抢性能有很大的优势,适用于单生产者多消费者情况
    2019-06-27
    1
  • Geek_f6fba7
    老师,我看了您的解释还是不明白,node数组和node数组里面的node节点以及node结点的的value都是被volatile修饰,为什么你说新增就未必能,能解释的详细点不,谢谢
    2019-12-01
  • 听雨
    老师,我的意思是Unsafe#compareAndSetObject和Unsafe#getObjectVolatile方法,这两个方法的volatile语义可以保证数组元素的可见性。这样即使新增一个node,这俩方法也可以保证其他线程可以读到。还是说我对这两个方法有误解,请老师解惑!

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

    2019-11-13
  • 听雨
    concurrentHashMap添加元素用了Unsafe#compareAndSetObject方法,获取元素使用Unsafe#getObjectVolatile方法,这两个方法不是都有volatile语义吗,按道理来说都能获取到最新值呀

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

    这里解释的很清楚了,已经存在的value是可以马上看到,但是一个新增的就未必能了。

    2019-11-13
  • 听雨
    老师,1.7ConcurrentHashMap弱一致性我理解,但是1.8为什么呀,1.8使用CAS可以保证可见性吧

    作者回复: ConcurrentHashMap是volatile关键字保证了可见性。

    我们知道Node<k,v>以及Node<k,v>的value是volatile修饰的,所以在一个线程对其进行修改后,另一个线程可以马上看到。
    如果是一个新Node,那么就不能马上看到,虽然Node的数组table被volatile修饰,但是这样只是代表table的引用地址如果被修改,其他线程可以立马看到,并不代表table里的数据被修改立马可以看到。—— 《加餐 | 什么是数据的强、弱一致性?》

    2019-11-12
  • 菜菜
    concurrentHashMap中的value不是加了volatile修饰吗? 为什么还存在数据弱一致性拿不到最新值的情况?

    作者回复: 在留言中已经解释过了,再翻一翻

    2019-09-22
  • 小笨蛋
    我有个疑问,你有提到ConcurrntHashMap里面数据量比较大的情况下会有很多的红黑树的转化操作,但是据我了解Java里面的Hash算法是采用的分散很好的Time33算法,这份hash还是很均匀的,应该不会出现大量的红黑树的转化工作。能麻烦老师解答一下吗?

    作者回复: 数据量大的情况下,再好的hash分散,链表的长度也会随着数据量的增加而变长

    2019-08-25
  • 老杨在努力
    个人感觉可以使用LinkedTransferQueue或者Disruptor来实现都可以,但是更倾向于后者,老师认为哪个更好??

    作者回复: Disruptor是一个高性能队列

    2019-08-16
  • 博弈
    老师,可以把几个QUENE讲一讲

    作者回复: 在答疑课堂已经做了补充

    2019-08-15
收起评论
39
返回
顶部