HashMap、HashTable和ConcurrentHashMap

HashMap

开启一个新篇幅来总结一下HashMap,毕竟这方面的知识很重要!
以提问的方式来学习

  1. HashMap的底层数据结构?
    数组+链表

  2. HashMap的存取原理?
    通过put(key,value)方法插入数组,通过get(key)获取节点。

  3. 怎么决定put键值对象放在哪里?
    在put插入的时候会通过哈希函数计算一个index值,即为插入的位置。
    计算index方法:(数组长度L-1)&hashcode

  4. 什么时候发生哈希碰撞?
    在插入节点时(先插入key为A),通过hashcode计算得到index为3,再插入节点(B),计算得到的index也是3,发生哈希碰撞。
    如果有两个对象通过计算index的位置相同时,就会发生哈希碰撞,这时两个对象形成了链表。
    在这里插入图片描述

  5. Java7和Java8的区别?
    Java8之前头插法,Java8之后尾插法
    对于上图,属于头插法:同一位置上新元素总会被放在链表的头部位置。
    数组里面存的Key-Value实例,在Java7叫Entry在Java8中叫Node。

  6. 默认初始化大小是多少?为啥大小都是2的幂?
    数组默认初始化大小为16.数组的长度为2次幂,如:1,4,8,16…
    为啥大小都是2的幂:因为在使用是2的幂的数字的时候,Length-1的值是所有二进制位全为1(比如15二进制1111),这种情况下,index的结果等同于HashCode后几位的值。
    只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。这是为了实现均匀分布。

  7. HashMap的扩容方式?负载因子是多少?
    当数据多次插入,会发生扩容。
    什么时候扩容:++size(put的个数)>=HashMap当前长度(Capacity)*0.75,触发扩容resize()。
    0.75:负载因子(LoadFactor)
    触发扩容之后,需要为每个元素key重新HashCode计算下标。

  8. 为什么HashMap线程不安全?
    使用头插法,在多个线程同时触发了扩容,在resize()时,
    线程1,将A、B放在了4位置,链表上顺序A-B
    线程2,将A、B放在了4位置,链表上顺序B-A
    就形成了循环列表 Infinite Loop。

使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。

Hashmap中的链表大小超过八个时会自动转化为红黑树,当删除小于六时重新变为链表


Hashmap是线程不安全的,那么我们在有多线程的时候,应该怎么办?
使用Collections.synchronizedMap(Map)创建线程安全的map集合:

  • HashTable
  • ConcurrentHashMap

HashTable

是线程安全的map集合,但是效率不高
原因是:HashTable对数据操作的时候都会上锁

public synchronized V get(Object key){
	Entry<?,?> tab[] = table;
	int hash = key.hashCode();
}

HashTable和HashMap的不同点:

  • Hashtable 不允许键或值为 null ,HashMap 的键值则都可以为 null。Hashtable在put 空值的时候会直接抛空指针异常
  • Hashtable 继承了 Dictionary类,而 HashMap 继承的是 AbstractMap 类
  • HashMap 的初始容量为:16,Hashtable 初始容量为:11,两者的负载因子默认都是:0.75
  • 当现有容量大于总容量 * 负载因子时,HashMap 扩容规则为当前容量翻倍,Hashtable 扩容规则为当前容量翻倍 + 1
  • HashMap 中的 Iterator 迭代器是 fail-fast 的,而 Hashtable 的 Enumerator 是 fail-safe的

什么是fail-fast(快速失败机制)?
是java集合中的一种机制, 在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。
原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
什么是fail-safe(安全失败机制)?


ConcurrentHashMap

底层结构:数组+链表
是由 Segment 数组、HashEntry 组成
在这里插入图片描述
HashEntry使用volatile去修饰了他的数据Value还有下一个节点next。
volatile:

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

Java1.7 ConcurrentHashMap的特点:

  1. 采用了分段锁技术,其中 Segment 继承于 ReentrantLock。ConcurrentHashMap 支持CurrencyLevel (Segment 数组数量)的线程并发
    如果数组容量大小是16,他的并发度就是16,可以同时允许16个线程操作16个Segment而且还是线程安全的。
  2. put操作过程:
    首先第一步的时候会尝试获取锁,如果获取失败肯定就有其他线程存在竞争,则利用 scanAndLockForPut() 自旋获取锁(1、尝试自旋获取锁。2、如果重试的次数达到了 MAX_SCAN_RETRIES 则改为阻塞锁获取,保证能获取成功。)。 先定位到Segment,再进行put操作。
    2.1、将当前 Segment 中的 table 通过 key 的 hashcode 定位到 HashEntry
    2.2、遍历该 HashEntry,如果不为空则判断传入的 key 和当前遍历的 key 是否相等,相等则覆盖旧的 value。
    2.3、不为空则需要新建一个 HashEntry 并加入到 Segment 中,同时会先判断是否需要扩容。
    2.4、释放锁
  3. get过程
    get 逻辑比较简单,只需要将 Key 通过 Hash 之后定位到具体的 Segment ,再通过一次 Hash 定位到具体的元素上。

Java1.8 ConcurrentHashMap的特点:

  1. 抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。
  2. put操作过程:
    2.1、根据 key 计算出 hashcode 。
    2.2、判断是否需要进行初始化。
    2.3、即为当前 key 定位出的 Node,如果为空表示当前位置可以写入数据,利用 CAS 尝试写入,失败则自旋保证成功。
    2.4、如果当前位置的 hashcode == MOVED == -1,则需要进行扩容。
    2.5、如果都不满足,则利用 synchronized 锁写入数据。
    2.6、如果数量大于 TREEIFY_THRESHOLD 则要转换为红黑树。
  3. get过程
    3.1、根据计算出来的 hashcode 寻址,如果就在桶上那么直接返回值。
    3.2、如果是红黑树那就按照树的方式获取值。
    3.3、就不满足那就按照链表的方式遍历获取值。

什么是CAS?
CAS 是乐观锁的一种实现方式
CAS 操作:线程在读取数据时不进行加锁,在准备写回数据时,比较原值是否修改,若未被其他线程修改则写回,若已被修改,则重新执行读取流程。
这是一种乐观策略,认为并发操作并不总会发生。CAS不一定能保证数据没有被修改过,比如遇到ABA问题。
什么是ABA问题?
一个线程把原值A改为了B,又来一个线程把B改回了原值,判断是否修改的线程过来发现并没有变化。
在涉及金钱的事情上还是影响挺大的。
怎么解决ABA问题呢?
查询原值的时候加上版本号或者时间戳,每次判断值加版本号\时间戳是否相同。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值