0
点赞
收藏
分享

微信扫一扫

对ConcurrentHashMap的理解

小暴龙要抱抱 2022-03-11 阅读 65

ConcurrentHashMap比HashMap并发好,比HashTable效率高,因为HashTable在对数据操作的时候都会上锁。

JDK1.7

JDK1.7 中的 ConcurrentHashMap 是segment+数组+链表的结构,即 ConcurrentHashMap 把哈希桶数组切分成小数组(Segment ),每个小数组有 n 个 HashEntry 组成。

原理上来说,ConcurrentHashMap 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。

不会像 HashTable 那样不管是 put 还是 get 操作都需要做同步处理,理论上 ConcurrentHashMap 支持 CurrencyLevel (Segment 数组数量)的线程并发。

每当一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。

就是说如果容量大小是16他的并发度就是16,可以同时允许16个线程操作16个Segment而且还是线程安全的。

JDK1.8

在数据结构上, JDK1.8 中的ConcurrentHashMap 选择了与 HashMap 相同的数组+链表+红黑树结构;在锁的实现上,抛弃了原有的 Segment 分段锁,采用CAS+synchronized实现更加细粒度的锁,如果多个线程访问的链表头结点不同,则不会冲突。

JDK1.7

  1. 尝试自旋获取锁。
  2. 如果重试的次数达到了上限则改为阻塞锁获取,保证能获取成功。

JDK1.8

  1. 根据key计算出hashcode。
  2. 判断是否需要进行初始化。
  3. 定位到Node,拿到首节点,判断首节点:
  •         如果为null,则通过CAS的方式尝试添加,失败则自旋保证成功。
  •         如果hashcode == MOVED == -1,说明其他线程在扩容,参与一起扩容。
  •         如果都不满足,则利用synchronized锁写入数据。

      4. 如果数量大于阈值则要转换为红黑树。

 JDK1.7

将key通过hash之后定位到具体的segment,再通过一次hash定位到具体的元素上。、

由于HashEntry中的value属性是用volatile关键词修饰的,保证了内存可见性,所以每次获取时都是新值,整个过程不用加锁。

JDK1.8

  1. 根据计算出来的hashcode寻址,如果就在桶上那么直接返回值。
  2. 如果是红黑树那就按照树的方式获取值。
  3. 都不满足那就按照链表的方式遍历获取值。

这是因为Hashtable使用的是安全失败机制(fail-safe),这种机制会使你此次读到的数据不一定是最新的数据。

如果你使用null值,就会使得其无法判断对应的key是不存在还是为空,因为你无法再调用一次contain(key)来对key是否存在进行判断,ConcurrentHashMap同理。

 但是HashMap却做了特殊处理。 

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

还可以使用Collections.synchronizedMap方法,对方法进行加同步锁。

如果传入的是 HashMap 对象,其实也是对 HashMap 做的方法做了一层包装,里面使用对象锁来保证多线程场景下,线程安全,本质也是对 HashMap 进行全表锁。

在竞争激烈的多线程环境下性能依然也非常差,不推荐使用! 

举报

相关推荐

0 条评论