Java并发编程实战
王宝令
资深架构师
立即订阅
15151 人已学习
课程目录
已完结 50 讲
0/4登录后,你可以任选4讲全文学习。
开篇词 (1讲)
开篇词 | 你为什么需要学习并发编程?
免费
学习攻略 (1讲)
学习攻略 | 如何才能学好并发编程?
第一部分:并发理论基础 (13讲)
01 | 可见性、原子性和有序性问题:并发编程Bug的源头
02 | Java内存模型:看Java如何解决可见性和有序性问题
03 | 互斥锁(上):解决原子性问题
04 | 互斥锁(下):如何用一把锁保护多个资源?
05 | 一不小心就死锁了,怎么办?
06 | 用“等待-通知”机制优化循环等待
07 | 安全性、活跃性以及性能问题
08 | 管程:并发编程的万能钥匙
09 | Java线程(上):Java线程的生命周期
10 | Java线程(中):创建多少线程才是合适的?
11 | Java线程(下):为什么局部变量是线程安全的?
12 | 如何用面向对象思想写好并发程序?
13 | 理论基础模块热点问题答疑
第二部分:并发工具类 (14讲)
14 | Lock和Condition(上):隐藏在并发包中的管程
15 | Lock和Condition(下):Dubbo如何用管程实现异步转同步?
16 | Semaphore:如何快速实现一个限流器?
17 | ReadWriteLock:如何快速实现一个完备的缓存?
18 | StampedLock:有没有比读写锁更快的锁?
19 | CountDownLatch和CyclicBarrier:如何让多线程步调一致?
20 | 并发容器:都有哪些“坑”需要我们填?
21 | 原子类:无锁工具类的典范
22 | Executor与线程池:如何创建正确的线程池?
23 | Future:如何用多线程实现最优的“烧水泡茶”程序?
24 | CompletableFuture:异步编程没那么难
25 | CompletionService:如何批量执行异步任务?
26 | Fork/Join:单机版的MapReduce
27 | 并发工具类模块热点问题答疑
第三部分:并发设计模式 (10讲)
28 | Immutability模式:如何利用不变性解决并发问题?
29 | Copy-on-Write模式:不是延时策略的COW
30 | 线程本地存储模式:没有共享,就没有伤害
31 | Guarded Suspension模式:等待唤醒机制的规范实现
32 | Balking模式:再谈线程安全的单例模式
33 | Thread-Per-Message模式:最简单实用的分工方法
34 | Worker Thread模式:如何避免重复创建线程?
35 | 两阶段终止模式:如何优雅地终止线程?
36 | 生产者-消费者模式:用流水线思想提高效率
37 | 设计模式模块热点问题答疑
第四部分:案例分析 (4讲)
38 | 案例分析(一):高性能限流器Guava RateLimiter
39 | 案例分析(二):高性能网络应用框架Netty
40 | 案例分析(三):高性能队列Disruptor
41 | 案例分析(四):高性能数据库连接池HiKariCP
第五部分:其他并发模型 (4讲)
42 | Actor模型:面向对象原生的并发模型
43 | 软件事务内存:借鉴数据库的并发经验
44 | 协程:更轻量级的线程
45 | CSP模型:Golang的主力队员
结束语 (1讲)
结束语 | 十年之后,初心依旧
用户故事 (2讲)
用户来信 | 真好,面试考到这些并发编程,我都答对了!
3 个用户来信 | 打开一个新的并发世界
Java并发编程实战
登录|注册

20 | 并发容器:都有哪些“坑”需要我们填?

王宝令 2019-04-13
Java 并发包有很大一部分内容都是关于并发容器的,因此学习和搞懂这部分的内容很有必要。
Java 1.5 之前提供的同步容器虽然也能保证线程安全,但是性能很差,而 Java 1.5 版本之后提供的并发容器在性能方面则做了很多优化,并且容器的类型也更加丰富了。下面我们就对比二者来学习这部分的内容。

同步容器及其注意事项

Java 中的容器主要可以分为四个大类,分别是 List、Map、Set 和 Queue,但并不是所有的 Java 容器都是线程安全的。例如,我们常用的 ArrayList、HashMap 就不是线程安全的。在介绍线程安全的容器之前,我们先思考这样一个问题:如何将非线程安全的容器变成线程安全的容器?
在前面《12 | 如何用面向对象思想写好并发程序?》我们讲过实现思路其实很简单,只要把非线程安全的容器封装在对象内部,然后控制好访问路径就可以了。
下面我们就以 ArrayList 为例,看看如何将它变成线程安全的。在下面的代码中,SafeArrayList 内部持有一个 ArrayList 的实例 c,所有访问 c 的方法我们都增加了 synchronized 关键字,需要注意的是我们还增加了一个 addIfNotExist() 方法,这个方法也是用 synchronized 来保证原子性的。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《Java并发编程实战》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(40)

  • 黑白尤文
    Java7中的HashMap在执行put操作时会涉及到扩容,由于扩容时链表并发操作会造成链表成环,所以可能导致cpu飙升100%。

    作者回复: 👍

    2019-04-13
    58
  • Grubby🐑
    这篇太简单了,但其实这些容器平时用的挺多的,希望老师后面能出一篇更加详细的介绍
    2019-04-13
    42
  • ykkk88
    没有理解为什么concurrentskiplistmap比concurrenthashmap性能好

    作者回复: 如果key冲突比较大,hashmap还是要靠链表或者tree来解决冲突的,所以O(1)是理想值。同时增删改操作很多也影响hashmap性能。这个也是要看冲突情况。也就是说hashmap的稳定性差,如果很不幸正好偶遇它的稳定性问题,同时又接受不了,就可以尝试skiplistmap,它能保证稳定性,无论你的并发量是多大,也没有key冲突的问题。

    2019-04-13
    22
  • 张天屹
    我理解的hashMap比其它线性容器更容易出问题是因为有扩容操作,存在更多竞态条件,所以如果条件满足时切换可能导致新生成很多数组,甚至可能出现链表闭环,这种情况可以查看堆栈,比如jstack查看会发现方法调用栈一直卡在HashMap的方法。另外上文迭代器遍历不安全是因为hasNext(size)和next()存在的竞态条件吗

    作者回复: 👍,不止是存在竞态条件,如果在遍历的时候出现修改操作,直接抛快速失败异常

    2019-04-13
    14
  • WolvesLeader
    个人认为您第二篇内存模型讲的非常棒,,,,,,,,,,

    作者回复: 我觉得自己理解起来困难而且对实际工作还有用的就会讲的深入一些,反之我觉得概念或者工具跟正常思维没有冲突,就会讲的简单,甚至略过。毕竟我们只是工具的使用者,首要问题是利用这些工具解决问题。感谢你的认可,我甚至觉得写完第二篇和管程之后就可以收工了,其他所有章节不过就是帮助大家进一步理解,从不同角度理解。

    2019-04-13
    11
  • CCC
    老师,用跳表实现的ConcurrentSkipListMap为什么可以做到无锁并发呢

    作者回复: 那个跳表就跟字典的索引一样,通过这个索引既能快速定位数据,也能隔离并发(可以并发查看不同页上的字)

    2019-04-13
    8
  • 木木匠
    jdk1.8以前的HashMap并发扩容的时候会导致陷入死循环,所以会导致cpu飙升,那么验证猜想我觉得有2种方法:
    1.线上查故障,用dump分析线程。
    2.用1.8以前的jdk在本地模拟。

    作者回复: 👍

    2019-04-13
    6
  • 龙猫
    java8之前的版本hashmap执行put方法时会有环形链表的风险,java8以后改成了红黑树

    作者回复: 👍

    2019-04-18
    5
  • 曾轼麟
    帮老师补充HashMap:当数据的HashCode 分布状态良好,并且冲突较少的时候对ConcurrentHashMap(查询,value修改,不包括插入),性能上基本上是和HashMap一致的,主要取决于分段锁的插思想。但是由于插入使用的是CAS的方式,所以如果对数据追加不多(插入)的情况下,建议可以考虑多使用ConcurrentHashMap避免由于修改数据产生一些意想不到的并发问题,当然内部也有保护机制通过抛出ConcurrentModificationException(快速失败机制)来让我们及时发现出现并发数据异常的情况,不知道我补充的是否正确。

    作者回复: 1.8版本之后ConcurrentHashMap的实现改了

    2019-04-13
    4
  • 陈华应
    选对容器的前提还是要对原理,特性,使用场景,优缺点,坑,甚至底层实现都了如指掌才能说选对容器,要不然更多的也是蒙对容器
    2019-04-13
    4
  • Liam
    LinkedTransferQueue有什么应用场景吗?

    作者回复: 实际工作中,为了防止OOM,基本上都使用有界队列,我工作中也没用过LinkedTransferQueue。

    2019-04-13
    4
  • 老师,我有个问题:
    文章里面说,使用CopyOnWriteArrayList时,需要能够容忍读写的短暂不一致,但是我理解CopyOnWriteArrayList应该不会出现不一致问题吧。因为底层的array是用volatile修饰的,根据happens-before原则,对volatile变量的写happens-before于对变量的读,也就是说如果存在并发读写的情况,写线程的setArray()一定是对读线程的getArray()可见的,所以我认为读到的始终都是最新的数据。
    不知道我的理解有没有问题?

    作者回复: 复制的时候允许读,可能读到数组里旧的元素。数组的引用是一致的,一旦设置就能读到,但是里面的元素会有不一致的情况

    2019-05-24
    1
    3
  • 我劝你善良
    老师,针对CopyOnWriteArrayList
    1.如果正在遍历的时候,同时有两个写操作执行,是会顺序在一个新数组上执行写操作,还是有两个写操作分别进行?如果是两个新数组的话,那么array又将指向哪一个新数组?
    2.如果在遍历的过程中,写操作已经完成了,但是遍历尚未结束,那么是array是直接指向新数组,并继续在新数组上执行未完成的遍历,还是等待遍历完成了,再修改array的指向呢?如果在遍历完之前就修改指向,那么就会存在问题了啊!

    作者回复: CopyOnWriteArrayList写操作是互斥的。

    2019-04-22
    1
    3
  • Geek_49a9e9
    老师,你好,最近两天,我线上跑的计费进程假死了(从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 查看 部分日志,如下:好像线程池所有线程都在等待执行,感觉一个数据库查询操作跑死了,很奇怪

    作者回复: 看这几行看不出来,一般问题都能通过线程栈发现问题。我遇到过生产者和消费者共用一个线程池,生产者把线程池里的线程用光了,导致消费不了。这种情况下通过线程池不太容易看,需要去计数。不知道你的问题是不是这个。所有线程都等待,还没有死锁,就查查为什么会等待吧。

    2019-04-20
    3
  • QQ怪
    除了jdk8之前因为并发导致的链表成环的问题还有一种可能是因为jdk8之前hash冲突还是使用的是链表,而jdk8之后使用了红黑树,开始还是生成链表,当链表长度为8时就会转变为红黑树,时间复杂度为O(logn),比链表效果好的多。

    作者回复: 是的,底层实现变了,我同事在1.8版本费了好大劲都没重现出来

    2019-04-13
    3
  • 周治慧
    hashmap在put的时候扩容导致链表的死环导致,可以通过遍历去entries中entry的next一直不为空来判断

    作者回复: 原因是对的,cpu飙升不降的问题都可以用dump线程栈来分析

    2019-04-13
    2
  • 李鑫磊
    既然 CopyOnWrite 的写操作是互斥的,那老师为什么在学习攻略中把 CopyOnWrite 归为无锁的方案?

    作者回复: 读无锁,写要看具体实现

    2019-09-11
    1
  • 月月月月
    老师,我想问下,文章里提到容器在遍历时要注意加锁保证线程安全,对于非线程安全的容器,我们可以通过包装让它变成线程安全的容器,然后在遍历的时候锁住集合对象。但是对于并发容器来说,在遍历的时候要怎么保证线程安全呢?如果还是锁住容器对象,但是对于不是使用synchronized去实现的并发容器,锁对象不就不一样了吗?那这样该怎么保证线程安全呢?

    作者回复: 并发容器的遍历是线程安全的

    2019-04-19
    1
  • crazypokerk
    满满干货,条理立马清晰了!
    2019-04-13
    1
  • Michael
    老师好!
    ThreadPoolExecutor的构造方法中传入的队列,什么场景下使用LinkedBlockingQueue有界队列,什么场景下使用ArrayBlockingQueue有界队列?如何选择?有什么依据?
    2019-12-01
收起评论
40
返回
顶部