这是我参与8月更文挑战的第30天,活动详情查看:8月更文挑战
关于HashMap
哈希表(hash table)
也叫散列表,是一种非常重要的数据结构,应用场景及其丰富,许多缓存技术(比如memcached)的核心其实就是在内存中维护一张大的哈希表, HashMap是一个key-value模型,具有映射关系,内部所用的数据结构是数组+链表/红黑树 ,通过key值可以找到value值,(把皇上当做key,它的那些妃子们就是很多个value,不太恰当,哈哈哈)并允许使用null值和null键,就是key和value的取值都可以为空,但key最多只能允许一个为空,多了的话就会覆盖;Hashtable的key就不允许为空。HashMap不保证映射的顺序,特别是它不保证该顺序恒久不变。
HashMap 的安全性
-
HashMap在多线程的环境下存在线程安全,一般如何处理
多线程下,由三种方式去实现安全的Map:
- 使用Collections.synchronizedMap(Map)创建线程安全的map集合
- HashTable
- ConcurrentHashMap
不过出于线程并发的原因,一般都会舍弃前两者去使用最后的ConcurrentHashMap,性能和效率明显高于前两者。
-
关于Collections.synchronizedMap 是如何实现线程安全的
在synchronizedMap 内部维护了一个Map对象和 排斥锁(Object),当传入了map参数时,将对象设置为Synchronized,并且内部的方法全部上了锁(重写了一套方法)
-
关于HashTable
跟HashMap相比,HashTable是线程安全的,适合在多线程的情况下使用,但是效率不太乐观
-
关于HashTable效率低的原因
在HashTable的源码方法里面,对数据的操作都会上锁(synchronized),所以效率会比较低下
ConcurrentHashMap详解
ConcurrentHashMap的底层是数组 + 链表组成的,不过在JDK1.7和1.8中有所不同
-
1.7 → 数组 + 链表
-
链表的数据结构与HashMap差不多,但是使用了volatile去修饰数据value和下一个节点next
-
volatile的特性是啥
- 可见性:A线程修改后,对于其他线程立刻可见
- 有序性:禁止指令重排序
- 只能保证对单次操作的原子性,i ++ 这种操作不能保证
-
-
关于ConcurrenthashMap 并发度高的原因
- 原理上来说,ConcurrentHashMap采用了分段锁技术,不会像HashTable那样不管是什么操作都同步处理(在多线程并发时,其他线程需要等待,容易造成阻塞或轮询状态)
- 每一个线程占用锁访问一个 Segment 时,不会影响到其他的 Segment。
- ConcurrentHashMap 和 HashTable 主要区别就是围绕着锁的粒度以及如何锁,ConcurrentHashMap锁的是单一的Segment , 而HashTable锁的是整个HashTable。 ConcurrentHashMap的put方法和HashMap的逻辑差不多,主要是新增了线程安全部分,在添加元素时候,采用synchronized来保证线程安全,然后计算size的时候采用CAS操作进行计算。
-
put流程小结:
1.判断key和vaule是否为空,如果为空,直接抛出异常。
2.判断table数组是否已经初始化完毕,如果没有初始化,进行初始化。
3.计算key的hash值,如果该位置为空,直接构造节点放入。
4.如果table正在扩容,进入帮助扩容方法。
5.最后开启同步锁,进行插入操作,如果开启了覆盖选项,直接覆盖,否则,构造节点添加到尾部,如果节点数超过红黑树阈值,进行红黑树转换。如果当前节点是树节点,进行树插入操作。
6.最后统计size大小,并计算是否需要扩容。
get小结
get时候不加锁,使用volatile来保证可见性,当要统计全局时(比如size),首先会尝试多次计算modcount来确定,这几次尝试中,是否有其他线程进行了修改操作,如果没有,则直接返回size。如果有,则需要依次锁住所有的Segment来计算。