HashMap? ConcurrentHashMap? 相信看完这篇没人能难住你!

2018-07-23 08:22:50 +08:00
 crossoverJie

https://crossoverjie.top/2018/07/23/java-senior/ConcurrentHashMap/

5343 次点击
所在节点    程序员
45 条回复
crossoverJie
2018-07-25 13:09:23 +08:00
@zpxshl #40 兄弟,我觉得这没有啥问题呀。



用 CAS 的前提是,遍历数组的时候在 key 所映射的那个位置是空的,也就是没有其他线程使用的时候才会在 2 处利用 CAS 尝试写入一个 Node。

也就是说写入成功的话是不会有线程竞争的,这样也就不会存在你说的可见性问题(如果这时有线程来做 get ,恰好也定位到这个位置,那在写入线程更新成功之前获取的肯定也是空)。

如果写入失败,CAS 返回 false,这样就会重新从 1 处再次循环。

这样如果当前位置有数据了那就会用 synchronized 来写,用 synchronized 的话也不会出现可见性问题。

如果当前位置依然为空的话就会重复上面的步骤。
zpxshl
2018-07-26 22:59:33 +08:00
@crossoverJie
a 线程,遍历数组的时候在 key 所映射的那个位置是空的,cas 插入成功。
b 线程,同样找到那个位置发现同样为空( cas 插入的数值对 b 线程不一定可见的),又 cas 插入,那不就出问题啦?
crossoverJie
2018-07-27 00:59:37 +08:00
@zpxshl #42

如果 A 线程写入成功之后 B 线程就算是恰巧在 A 线程还未写入之前判断到是空进入了 CAS 的逻辑。

由于最终调用的还是 casTabAt()



底层调用的是 unsafe.compareAndSwapObject 方法,他的期望是当前值为 null ,才会写入一个新的 Node。

由于该方法是线程安全的,此时当前值已经不为 null 了,所以必定会写入失败。

还有一个是,虽然是用 volatile 修饰的数组,但是用的是 getObjectVolatile 和 putObjectVolatile,依然可以保证可见性。

见:

https://stackoverflow.com/questions/31534706/about-unsafe-getobjectvolatile-usage
zpxshl
2018-07-27 15:37:01 +08:00
@crossoverJie 感谢!
c4f36e5766583218
2019-03-30 00:38:38 +08:00
jdk7 先扩容再 put 值;
```
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
```
jdk8 先 put 值再扩容: ```java.util.HashMap#putVal(int, K, V, boolean, boolean)```

为什么?这么改动?有什么区别吗?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/473186

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX