并发 List、Map源码解析和总结

1 CopyOnWriteArrayList 相关

1.1 和 ArrayList 相比有哪些相同点和不同点?

答:相同点:底层的数据结构是相同的,都是数组的数据结构,提供出来的 API 都是对数组结
构进行操作,让我们更好的使用。
不同点:后者是线程安全的,在多线程环境下使用,无需加锁,可直接使用。

1.2 CopyOnWriteArrayList 通过哪些手段实现了线程安全?

答:主要有如下:
1)数组容器被 volatile 关键字修饰,保证了数组内存地址被任意线程修改后,都会通知到其他线程;
2) 对数组的所有修改操作,都进行了加锁,保证了同一时刻,只能有一个线程对数组进行修
改,比如我在 add 时,就无法 remove;
3) 修改过程中对原数组进行了复制,是在新数组上进行修改的,修改过程中,不会对原数组产
生任何影响。
通过以上三点保证了线程安全。

1.3 在 add 方法中,对数组进行加锁后,不是已经是线程安全了么,为什么还需要 对老数组进行拷贝?

答:1)对数组进行加锁后,能够保证同一时刻,只有一个线程能对数组进行 add,在同单 核 CPU 下的多线程环境下肯定没有问题,
2)但我们现在的机器都是多核 CPU,如果我们不通过复制拷贝新建数组,修改原数组容器的内存地址的话,是无法触发 volatile 可见性效果的,那么其他 CPU 下的线程就无法感知数组原来已经被修改了,就会引发多核 CPU 下的线程安全问题。
3)假设我们不复制拷贝,而是在原来数组上直接修改值,数组的内存地址就不会变,而数组被 volatile 修饰时,必须当数组的内存地址变更时,才能及时的通知到其他线程,内存地址不变,
仅仅是数组元素值发生变化时,是无法把数组元素值发生变动的事实,通知到其它线程!

1.4 对老数组进行拷贝,会有性能损耗,我们平时使用需要注意什么么?

答:主要有如下注意点:
1)在批量操作时,尽量使用 addAll、removeAll 方法,而不要在循环里面使用 add、remove
方法,主要是因为 for 循环里面使用 add 、remove 的方式,在每次操作时,都会进行一次
数组的拷贝(甚至多次),非常耗性能,而 addAll、removeAll 方法底层做了优化,整个操作
只会进行一次数组拷贝,由此可见,当批量操作的数据越多时,批量方法的高性能体现的越
明显。

1.5 为什么 CopyOnWriteArrayList 迭代过程中,数组结构变动,不会抛出 ConcurrentModificationException 了

答:主要是因为 CopyOnWriteArrayList 每次操作时,都会产生新的数组,而迭代时,持有的
仍然是老数组的引用,所以我们说的数组结构变动,是用新数组替换了老数组,老数组的结构并
没有发生变化,所以不会抛出异常了。

1.6 插入的数据正好在 List 的中间,请问两种 List 分别拷贝数组几次?为什么?

答:1)ArrayList 只需拷贝一次,假设插入的位置是 2,只需要把位置 2 (包含 2)后面的数据都往后移动一位即可,所以拷贝一次。
2)CopyOnWriteArrayList 拷贝两次,因为 CopyOnWriteArrayList 多了把老数组的数据拷贝到 新数组上这一步,可能有的同学会想到这种方式:先把老数组拷贝到新数组,再把 2 后面的数 据往后移动一位,这的确是一种拷贝的方式
3)CopyOnWriteArrayList 底层实现更加灵活, 而是:把老数组 0 到 2 的数据拷贝到新数组上,预留出新数组 2 的位置,再把老数组 3~ 最后的数据拷贝到新数组上,这种拷贝方式可以减少我们拷贝的数据,虽然是两次拷贝,但拷贝的数据却仍然是老数组的大小,设计的非常巧妙。

2 ConcurrentHashMap 相关

2.1 ConcurrentHashMap 和 HashMap 的相同点和不同点

答:相同点:
1)都是数组 + 链表 +红黑树的数据结构,所以基本操作的思想相同;
2)都实现了 Map 接口,继承了 AbstractMap 抽象类,所以两者的方法大多都是相似的,可以
互相切换。
不同点:
1)ConcurrentHashMap 是线程安全的,在多线程环境下,无需加锁,可直接使用;
2)数据结构上,ConcurrentHashMap 多了转移节点,主要用于保证扩容时的线程安全。

2.2 ConcurrentHashMap 通过哪些手段保证了线程安全。

答:主要有以下几点:
1)储存 Map 数据的数组被 volatile 关键字修饰,一旦被修改,立马就能通知其他线程,因为是
数组,所以需要改变其内存值,才能真正的发挥出 volatile 的可见特性;
2)put 时,如果计算出来的数组下标索引没有值的话,采用无限 for 循环 + CAS 算法,来保证
一定可以新增成功,又不会覆盖其他线程 put 进去的值;
3)如果 put 的节点正好在扩容,会等待扩容完成之后,再进行 put ,保证了在扩容时,老数组
的值不会发生变化;
4)对数组的槽点进行操作时,会先锁住槽点,保证只有当前线程才能对槽点上的链表或红黑树
进行操作;
5)红黑树旋转时,会锁住根节点,保证旋转时的线程安全。

2.3 描述一下 CAS 算法在 ConcurrentHashMap 中的应用?

答:CAS 其实是一种乐观锁,一般有三个值,分别为:赋值对象,原值,新值,在执行的时
候,会先判断内存中的值是否和原值相等,相等的话把新值赋值给对象,否则赋值失败,整个过
程都是原子性操作,没有线程安全问题。
ConcurrentHashMap 的 put 方法中,有使用到 CAS ,是结合无限 for 循环一起使用的,步
骤如下:
1)计算出数组索引下标,拿出下标对应的原值;
2)CAS 覆盖当前下标的值,赋值时,如果发现内存值和 1 拿出来的原值相等,执行赋值,退出
循环,否则不赋值,转到 3;
3)进行下一次 for 循环,重复执行 1,2,直到成功为止。
可以看到这样做的好处,第一是不会盲目的覆盖原值,第二是一定可以赋值成功。

2.4 ConcurrentHashMap 是如何发现当前槽点正在扩容的。

答:ConcurrentHashMap 新增了一个节点类型,叫做转移节点,当我们发现当前槽点是转移
节点时(转移节点的 hash 值是 -1),即表示 Map 正在进行扩容。

2.5 发现槽点正在扩容时,put 操作会怎么办?

答:无限 for 循环,或者走到扩容方法中去,帮助扩容,一直等待扩容完成之后,再执行 put
操作。

2.6 两种 Map 扩容时,有啥区别?

答:区别很大,HashMap 是直接在老数据上面进行扩容,多线程环境下,会有线程安全的问
题,而 ConcurrentHashMap 就不太一样,扩容过程是这样的:
1)从数组的队尾开始拷贝;
2)拷贝数组的槽点时,先把原数组槽点锁住,拷贝成功到新数组时,把原数组槽点赋值为转移
节点;
3)从数组的尾部拷贝到头部,每拷贝成功一次,就把原数组的槽点设置成转移节点;
4)直到所有数组数据都拷贝到新数组时,直接把新数组整个赋值给数组容器,拷贝完成。
简 单 来 说 , 通 过 扩 容 时 给 槽 点 加 锁 , 和 发 现 槽 点 正 在 扩 容 就 等 待 的 策 略 , 保 证 了 ConcurrentHashMap 可以慢慢一个一个槽点的转移,保证了扩容时的线程安全,转移节点比较重要,平时问的人也比较多。

2.7 ConcurrentHashMap 在 Java 7 和 8 中关于线程安全的做法有啥不同?

答:非常不一样,拿 put 方法为例,Java 7 的做法是:
1)把数组进行分段,找到当前 key 对应的是那一段;
2)将当前段锁住,然后再根据 hash 寻找对应的值,进行赋值操作。
Java 7 的做法比较简单,缺点也很明显,就是当我们需要 put 数据时,我们会锁住改该数据对 应的某一段,这一段数据可能会有很多,比如我只想 put 一个值,锁住的却是一段数据,导致 这一段的其他数据都不能进行写入操作,大大的降低了并发性的效率。Java 8 解决了这个问
题,从锁住某一段,修改成锁住某一个槽点,提高了并发效率。
不仅仅是 put,删除也是,仅仅是锁住当前槽点,缩小了锁的范围,增大了效率。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容