HashMap&ConcurrentHashMap

1. HashMap

  1. put 的时候是插链表。

  2. ReHash 的时候也是头插链表。
    在jdk1.8之前是插入头部的,在jdk1.8中是插入尾部的。

  3. 多线程 put 链表会成环,获取环链表上的某个k时会出现死循环。

  4. 多线程put 可能导致某个值被覆盖或丢失。

  5. 初始容量为16,负载因子为 0.75,超过负载扩容一倍
    为什么初始是16:
    int index =key.hashCode()&(length-1);
    1 减少hash碰撞
    2 提高map查询效率
    3 分配过小防止频繁扩容
    4 分配过大浪费资源

  6. 为什么链表的长度为8是变成红黑树?为什么为6时又变成链表?
    1)理想情况下随机hashCode算法下所有bin中节点的分布频率会遵循泊松分布,我们可以看到,一个bin中链表长度达到8个元素的概率为0.00000006,几乎是不可能事件。
    2)红黑树的平均查找长度是log(n);链表长度查询的平均长度为 n/2
    log(8)=3 8/2=4 为8时才多查找一次
    log(6)=2.6 6/2=3

2. ConcurrentHashMap

2.1. JDK1.8:

中ConcurrentHashMap取消了segment分段锁,而采用CAS和synchronized来保证并发安全。数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树。
只要不发生hash不冲突,就不会出现并发获得锁的情况。它首先使用无锁操作CAS插入头结点,如果插入失败,说明已经有别的线程插入头结点了,再次循环进行操作。如果头结点已经存在,则通过synchronized获得头结点锁,进行后续的操作。性能比segment分段锁又再次提升
在这里插入图片描述
put操作:

  1. 判断Node[]数组是否初始化,没有则进行初始化操作
  2. 通过hash定位Node[]数组的索引坐标,是否有Node节点,如果没有则使用CAS进行添加(链表的头结点),添加失败则进入下次循环。
  3. 检查到内部正在扩容,如果正在扩容,就帮助它一块扩容。
  4. 如果f!=null,则使用synchronized锁住f元素(链表/红黑二叉树的头元素)
    4.1 如果是Node(链表结构)则执行链表的添加操作。
    4.2 如果是TreeNode(树型结果)则执行树添加操作。
  5. 判断链表长度已经达到临界值8 就需要把链表转换为树结构。

CAS
如果从内存地址V取值,原来值为A,修改为B,之后又将值修改为A,CAS会认为该值从来没变过。
解决思路:加版本号。

2.2. JDK 1.7:

1.一个CocurrentHashMap(CHM)包含一个Segment数组;一个Segment元素里包含一个HashEntry数组;每个HashEntry是一个链表结构的元素。
2. HashEntry则用于存储键值对数据;
3. Segment 继承可重入锁(ReentrantLock),扮演锁的角色;。

2.3. 为什么get方法不需要加锁?

get方法将要使用的共享变量都定义成了volatile类型,

transient volatile int count;
volatile V value;

定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值。get操作里只需要读不需要写共享变量count和value,所以可以不用加锁。
Java内存模型的happen before原则,对volatile字段的写操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的

2.3.1 volatile

volatile 的特性

  • 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。(可见性
  • 禁止进行指令重排序。(实现有序性)
  • volatile 只能保证对单次读/写的原子性。i++ 这种操作不能保证原子性。

volatile 的实现原理:
1)volatile 可见性实现

  • volatile 变量的内存可见性是基于内存屏障(Memory Barrier)实现。
    内存屏障,是一个 CPU 指令。
    JMM 为了保证在不同的编译器和 CPU 上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序

2)volatile 有序性实现

  • happens-before 规则中有一条是 :对一个 volatile 域的写,happens-before 于任意后续对这个 volatile 域的读。
  • volatile 禁止重排序
    (1)
    volatile写插入指令
    (2)
    volatile写插入指令
HashMapConcurrentHashMap都是Java中常用的Map实现类,它们有以下几个主要区别: 1. 线程安全性:HashMap是非线程安全的,而ConcurrentHashMap是线程安全的。在多线程环境下使用HashMap可能会导致竞态条件和数据不一致的问题,而ConcurrentHashMap通过使用锁分段技术(Segment)或CAS操作来实现线程安全。 2. 并发性能:ConcurrentHashMap在并发访问能够提供较好的性能。它通过将数据分割成多个段(Segment),每个段都拥有自己的锁,不同的线程可以同访问不同的段,从而提高并发性能。而HashMap在并发访问需要手动添加同步措施,性能较低。 3. 迭代器弱一致性:ConcurrentHashMap的迭代器是弱一致的,即在迭代过程中允许其他线程对Map进行修改,但不保证迭代器一定能够反映出最新的修改。而HashMap的迭代器是快速失败的,即在迭代过程中如果有其他线程对Map进行修改,会立即抛出ConcurrentModificationException异常。 4. Null键和null值:HashMap允许使用null作为键和值,而ConcurrentHashMap不允许使用null作为键和值。在ConcurrentHashMap中,如果使用null作为键或值,可能会抛出NullPointerException异常。 综上所述,HashMap适用于单线程环境或者在多线程环境下通过手动添加同步措施保证线程安全;而ConcurrentHashMap适用于多线程环境下需要高并发性能和线程安全性的场景。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值