内容总结于敖丙:
https://mp.weixin.qq.com/s/AixdbEiXf3KfE724kg2YIw
多线程下能够代替hashmap的有哪些?
三种
- 使用Collections.synchonizedMap()去把一个map类型转换成一个安全的map集合(所有操作加互斥锁)
- 使用hashtable
- 使用ConcurrentHashMap
但由于前两者都是单纯的给所有操作加上锁来保证并发安全,效率并不高。
咱一般使用第三种.
Collections.synchonizedMap()如何实现线程安全?
内部维护了一个map对象(也就是我们调用工具类的时候传进来的),然后自己也维护了一个互斥锁,对创建出来的map集合的所有操作都会加上锁。
那HashTable呢?
hashtable用的人很少,其实现其实也是给所有方法加上锁
除了线程安全,HashTable和HashMap有啥区别呢?
HashTable put进去的key和value都不能为null,而hashmap可以。
为啥HashTable不能呢(ConcurrentHashMap也不行哈),因为它们都是用于并发的,但如果能为null的话,并不能知道到底是没有这个key,还是这个key对应的值为null,因为在get之后,并不能再一次用containKey去验证它(并发情况中间可能都进行了多次操作了)
而hashmap本来就是服务于单线程的,所以不行就不行拉,破罐子破摔。
ConcurrentHashMap的数据结构是咋样的,为啥并发度这么高?
1.7 segment数组+HashEntry,也是数组+链表的形式,只不过由于segment这个东西的存在,实现了分段锁的机制,我理解的是在hashtable的样子上加了一个【维度】,即把每个segment看作是一个hashtable,每次上锁只用锁上一个segement,其他的segment正常使用。比如有16个segment,就同时支持16个线程的并发。(要注意个别方法还是会锁住整个表如size)
还有不同的是这里的HashEntry的value字段是加上了volatile关键字的,保证了取节点时的可见性。同时也只有value字段是没有加上final修饰的,这说明修改节点可以直接修改,但是因为next是被final修饰的,删除节点只能把前续节点全部复制,所以remove比较耗时。
put操作呢是先hash,找到位置之后自旋获取锁,自旋到一定次数转化为阻塞锁。获取到锁之后再次hash得到位置,放置节点,判断是否初始化、扩容。
get操作也是两次hash找位置,但是特点呢是全程无锁,因为上面提到了使用volatile 保证了可见性。
也正式因为volatile,使用ConcurrentHashMap可要小心哦比如并发情况的 map.put(1,map.get(1)+1) 是会出错的,因为volatile没保证原子性。
1.8 抛弃了笨重的segment,采用CAS和Synchronized来保证安全性。
面试的时候可能会牵扯出CAS,ABA,Synchronized,锁升级等等,任重道远呀。