20 | 并发容器:都有哪些“坑”需要我们填?
20 | 并发容器:都有哪些“坑”需要我们填?
讲述:王宝令
时长10:01大小9.15M
同步容器及其注意事项
并发容器及其注意事项
(一)List
(二)Map
(三)Set
(四)Queue
总结
课后思考
赞 36
提建议
精选留言(50)
- 黑白尤文2019-04-13Java7中的HashMap在执行put操作时会涉及到扩容,由于扩容时链表并发操作会造成链表成环,所以可能导致cpu飙升100%。
作者回复: 👍
共 2 条评论170 - Grubby🐑2019-04-13这篇太简单了,但其实这些容器平时用的挺多的,希望老师后面能出一篇更加详细的介绍106
- ykkk882019-04-13没有理解为什么concurrentskiplistmap比concurrenthashmap性能好
作者回复: 如果key冲突比较大,hashmap还是要靠链表或者tree来解决冲突的,所以O(1)是理想值。同时增删改操作很多也影响hashmap性能。这个也是要看冲突情况。也就是说hashmap的稳定性差,如果很不幸正好偶遇它的稳定性问题,同时又接受不了,就可以尝试skiplistmap,它能保证稳定性,无论你的并发量是多大,也没有key冲突的问题。
共 6 条评论89 - 张天屹2019-04-13我理解的hashMap比其它线性容器更容易出问题是因为有扩容操作,存在更多竞态条件,所以如果条件满足时切换可能导致新生成很多数组,甚至可能出现链表闭环,这种情况可以查看堆栈,比如jstack查看会发现方法调用栈一直卡在HashMap的方法。另外上文迭代器遍历不安全是因为hasNext(size)和next()存在的竞态条件吗
作者回复: 👍,不止是存在竞态条件,如果在遍历的时候出现修改操作,直接抛快速失败异常
共 2 条评论46 - WolvesLeader2019-04-13个人认为您第二篇内存模型讲的非常棒,,,,,,,,,,
作者回复: 我觉得自己理解起来困难而且对实际工作还有用的就会讲的深入一些,反之我觉得概念或者工具跟正常思维没有冲突,就会讲的简单,甚至略过。毕竟我们只是工具的使用者,首要问题是利用这些工具解决问题。感谢你的认可,我甚至觉得写完第二篇和管程之后就可以收工了,其他所有章节不过就是帮助大家进一步理解,从不同角度理解。
共 5 条评论38 - 龙猫2019-04-18java8之前的版本hashmap执行put方法时会有环形链表的风险,java8以后改成了红黑树
作者回复: 👍
共 3 条评论32 - CCC2019-04-13老师,用跳表实现的ConcurrentSkipListMap为什么可以做到无锁并发呢
作者回复: 那个跳表就跟字典的索引一样,通过这个索引既能快速定位数据,也能隔离并发(可以并发查看不同页上的字)
21 - Liam2019-04-13LinkedTransferQueue有什么应用场景吗?
作者回复: 实际工作中,为了防止OOM,基本上都使用有界队列,我工作中也没用过LinkedTransferQueue。
17 - QQ怪2019-04-13除了jdk8之前因为并发导致的链表成环的问题还有一种可能是因为jdk8之前hash冲突还是使用的是链表,而jdk8之后使用了红黑树,开始还是生成链表,当链表长度为8时就会转变为红黑树,时间复杂度为O(logn),比链表效果好的多。
作者回复: 是的,底层实现变了,我同事在1.8版本费了好大劲都没重现出来
共 2 条评论15 - 罗洲2019-04-13jdk1.8以前的HashMap并发扩容的时候会导致陷入死循环,所以会导致cpu飙升,那么验证猜想我觉得有2种方法: 1.线上查故障,用dump分析线程。 2.用1.8以前的jdk在本地模拟。
作者回复: 👍
14 - 张申傲2019-05-24老师,我有个问题: 文章里面说,使用CopyOnWriteArrayList时,需要能够容忍读写的短暂不一致,但是我理解CopyOnWriteArrayList应该不会出现不一致问题吧。因为底层的array是用volatile修饰的,根据happens-before原则,对volatile变量的写happens-before于对变量的读,也就是说如果存在并发读写的情况,写线程的setArray()一定是对读线程的getArray()可见的,所以我认为读到的始终都是最新的数据。 不知道我的理解有没有问题?展开
作者回复: 复制的时候允许读,可能读到数组里旧的元素。数组的引用是一致的,一旦设置就能读到,但是里面的元素会有不一致的情况
共 3 条评论10 - Randy2019-11-14留言中很多都提到在JDK1.8以前会存在HashMap的并发情况下resize可能导致死循环问题,其实这个问题在1.8中也存在,并没有因为在1.8中引入了红黑树而规避掉。因为导致问题的原因是resize方法调用了transfer,也就是说是发生在链表的重组过程,跟红黑树没有关系。所以JDK1.8中还是存在这个问题 请宝令老师指正
作者回复: 刚好有篇公众号讲到这个问题,写的非常好。https://mp.weixin.qq.com/s/yxn47A4UcsrORoDJyREEuQ
共 3 条评论9 - candy2019-04-20老师,你好,最近两天,我线上跑的计费进程假死了(从1月11日开始跑的,4月10日第一次出现假死), ExecutorService services = Executors.newFixedThreadPool(taskThreads); CountDownLatch cdt = new CountDownLatch(size); //一个个的处理数据 for (int j = 0; j < size; j++) { CFTask task = new CFTask(table, channelIds.get(j), batchId, cdt); services.submit(task); } cdt.await(); 这个有什么错误吗?让多个线程处理步调一致 线上jstack pid 查看 部分日志,如下:好像线程池所有线程都在等待执行,感觉一个数据库查询操作跑死了,很奇怪展开
作者回复: 看这几行看不出来,一般问题都能通过线程栈发现问题。我遇到过生产者和消费者共用一个线程池,生产者把线程池里的线程用光了,导致消费不了。这种情况下通过线程池不太容易看,需要去计数。不知道你的问题是不是这个。所有线程都等待,还没有死锁,就查查为什么会等待吧。
共 6 条评论9 - 南山2019-04-13选对容器的前提还是要对原理,特性,使用场景,优缺点,坑,甚至底层实现都了如指掌才能说选对容器,要不然更多的也是蒙对容器8
- 我劝你善良2019-04-22老师,针对CopyOnWriteArrayList 1.如果正在遍历的时候,同时有两个写操作执行,是会顺序在一个新数组上执行写操作,还是有两个写操作分别进行?如果是两个新数组的话,那么array又将指向哪一个新数组? 2.如果在遍历的过程中,写操作已经完成了,但是遍历尚未结束,那么是array是直接指向新数组,并继续在新数组上执行未完成的遍历,还是等待遍历完成了,再修改array的指向呢?如果在遍历完之前就修改指向,那么就会存在问题了啊!展开
作者回复: CopyOnWriteArrayList写操作是互斥的。
共 2 条评论7 - 月月月月2019-04-19老师,我想问下,文章里提到容器在遍历时要注意加锁保证线程安全,对于非线程安全的容器,我们可以通过包装让它变成线程安全的容器,然后在遍历的时候锁住集合对象。但是对于并发容器来说,在遍历的时候要怎么保证线程安全呢?如果还是锁住容器对象,但是对于不是使用synchronized去实现的并发容器,锁对象不就不一样了吗?那这样该怎么保证线程安全呢?
作者回复: 并发容器的遍历是线程安全的
6 - Sunny_Lu2019-04-17感觉copyonwrite的使用限制很大,首先要容忍暂时的不一致,数据量大的情况下,写一旦多一点 不断的复制也会有性能上的问题吧?还是说多次的写用的是同一个数组呢
作者回复: 👍,多个数组
6 - 曾轼麟2019-04-13帮老师补充HashMap:当数据的HashCode 分布状态良好,并且冲突较少的时候对ConcurrentHashMap(查询,value修改,不包括插入),性能上基本上是和HashMap一致的,主要取决于分段锁的插思想。但是由于插入使用的是CAS的方式,所以如果对数据追加不多(插入)的情况下,建议可以考虑多使用ConcurrentHashMap避免由于修改数据产生一些意想不到的并发问题,当然内部也有保护机制通过抛出ConcurrentModificationException(快速失败机制)来让我们及时发现出现并发数据异常的情况,不知道我补充的是否正确。展开
作者回复: 1.8版本之后ConcurrentHashMap的实现改了
5 - Sharry2019-09-17在 JDK7 上 HashMap 数据迁移时, 会导致结点翻转, 多线程操作时, 可能会引发死循环 , 在 JDK8 上修改了数据迁移的算法, 保证数据的顺便, 不会出现死循环问题
作者回复: 👍
4 - 李鑫磊2019-09-11既然 CopyOnWrite 的写操作是互斥的,那老师为什么在学习攻略中把 CopyOnWrite 归为无锁的方案?
作者回复: 读无锁,写要看具体实现
4