HashMap学习笔记:HashTable&ConcurrentHashMap

1.为什么hashmap中链表大小超过8个会自动转化为红黑树,当删除小于6个会重新转换为链表?
根据泊松分布:是一种统计与概率学里常见到的离散概率分布。在负载因子默认为75%时,单个hash槽内元素个数为8的概率小于百万分之一,所以7时个分水岭,等于7时不转换,大于7时转换,小于7时转换为链表。

2.hashma在多线程环境下是不安全的,如何处理这种情况?

  • Collections.sysnchronizedMap(Map):创建线程安全的集合
  • HashTable
  • ConcurrentHashMap

3.Collections.sysnchronizedMap(Map)的实现
sysnchronizedMap内部维护了拥有两个属性:

  1. 普通的Map对象
  2. mutex排斥锁
    这个类有两个构造方法,若不传递一个mutex参数,则会把mutex赋值为this。

创建完成后,再操作map的时候就会对方法上锁sysnchronized

4.为什么说HashTable效率低?
当HashTable对数据进行操作时都会上锁。

5.HashTable与hashMap的区别?

  • HashTable不允许键值为null,hashMap则可以为null

1.因为HashTable在put空值的时候会直接报空指针异常,hashMap则做了特殊处理,默认null会赋值0;
2.HashTable使用安全失败机制(fail-safe),这种机制在读取数据的时候不一定是最新的值。如果你使用null值。就无法判断key是不存在还是为空,因为你无法再调用一次contain(key)来进行判断。

  • 实现方式不通,分别集成不同的类:HashTable extends Dictionary、hashMap extends AbstractMap
  • 初始化容量不同:HashTable ->11,hashMap ->16
  • 扩展机制不同:HashTable-> 2oldSize+1, hashMap -> 2oldSzie
  • 迭代器不同:HashTable中的Iterator迭代器是fail-fast,HashTable则不会

6.什么是fail-fast,原理是什么?

  • 快速失败是java集合中的一种机制,再用迭代器遍历一个集合的时候,如果对集合对象的内容进行了增加删除修改,则会抛出ConcurrentModificationException
  • 迭代器在遍历时直接访问集合中的内容,会使用一个modCount的变量;迭代器每次next遍历下一个元素时会检测这个遍历是否被改变,如果改变了就会抛出异常,终止遍历。

7.fail-safe
JUC(java.util.concurrent)包下面的容器都是安全失败的,可以在多线程并发使用,并发修改

8.ConcurrentHashMap它的数据结构是怎样的?为什么并发度那么高?

  1. jdk7中数据结构:Segment数组+hashEntry链表

1.segment数组是ConcurrentHashMap的一个内部类
2.HashEntry与hashMap中的差不多,唯一不同点是用了volatile修饰它的数据value以及下一个节点next。
3.并发高的原因:

  • 原理上:采用了分段锁技术,其中Segment继承了ReentrantLock。当每个线程访问其中一个Segment时,不会影响其他Segment。理论来说,容器大小有多少,它的Segment就有多少,就可以同时允许对等的线程进行操作,并且保证安全性。

4.put逻辑:

  • (1)首先第一步会尝试获取锁,如果获取锁失败那么会利用scanAndLockForPut() 自旋获取锁:1.尝试自旋获取锁,2.如果重试次数达到了max_scan_retries则改为阻塞锁获取,保证能获取成功

  • (2)首先定位到Segment,每个Segment对应这一个HashEntry
    数组

  • (3)将当前Segment中的table通过key的hashCode计算出hashEntry的下标,定位到hashEntry中的某一个节点(链表头)

  • (4)遍历链表

  • (5)若此时链表为空,则判断是否需要扩容并且把新节点插入到 链表头中,根据key-value形成节点插入链表中。

  • (6)若不为空,则遍历判断传入的key与当前遍历的key是否相同,相同则覆盖旧的value

  • (7)最后释放锁

5.get逻辑:

  • (1)只需要把key通过hash定位到具体的Segment,再通过hash定位到具体的元素上即可。
  • (2)因为hashEntry中的value是被volatile修饰的,保证了内存可见性,每次获取都是最新值。
  • (3)因此整个get过程不需要加锁。
  1. jdk8中抛弃了原有的Segment分段锁,而采用了CAS+synchronized来保证并发安全
  • 把hashEntry变成了node,值和next都被volatile修饰,保证了可用性,并且引入了红黑树,在链表大于一定值的时候会自动转换(默认为8)

  • 1.put逻辑:

  • (1)根据key计算hashCode

  • (2)判断是否需要进行初始化。

  • (3)即当前key定位出来的node。如果为空则表示可以直接写入数据,利用CAS尝试写入,失败则自旋保证成功。

  • (4)如果当前位置的 hoshcode == moved == -1, 则需要进行扩容。

  • (5)如果以上都不满足,则利用synchronized锁进入数据。

  • (6)如果数量大于 treeify_threshold则转换为红黑树。

  • 2.get逻辑:

  • (1)根据计算出来的hashCode去寻找地址,如果在桶上面有值则直接返回。

  • (2)如果在红黑树那就按照树的方式找值。

  • (3)如果还不满足,那就按照链表的方式去循环取值。

9.volatile起什么作用?

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

10.CAS是什么?
CAS是乐观锁的一种实现方式,是一种轻量级锁,操作如下:

  • 1.线程在读取数据不进行加锁
  • 2.在写回数据的时,比较原值是否被更改
  • 3.若被更改则重新执行一边读取流程
  • 4.若未被其他线程修改则返回

这属于一种乐观策略,认为并发操作并不是总发生,适用于多读少些的场景。

11.CAS一定能保证数据没有被其他线程修改过么?
以典型的ABA实例:

  • 1.初始值为A
  • 2.线程1把A改成B
  • 3.同时线程2又把B改成了A
  • 4.最终结果是正确的,可以正常返回,但是无法记录其中的一个修改过程。

12.如何解决ABA问题
添加版本号:在修改查询他原来的值的时候,顺便带上一个版本号,每次判断包括原值+版本号,若成功,版本号则+1。

13.jdk1.8升级后为什么synchronized反而使用更多?
synchronized在此之前一直是重量级的锁,但是现在更新采用了锁升级的方式:

  • 1.先使用偏向锁优先同一线程然后再次获取锁,若失败。
  • 2.升级为CAS轻量级锁,如果失败就短暂自旋,防止线程被系统挂起,若失败,
  • 3.最终升级为重量级锁。

因此jdk1.8进行了优化,锁是一步一步升级上去的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值