HashMap 可以允许key为null,value为null,但HashMap的是线程不安全的 HashMap 底层是数组 + 链表的数据结构
- 在jdk 1.7 中 map集合中的每一项都是一个 entry
- 在jdk 1.8 中 map 集合中的每一项都是一个node
这张图我解释一下 在每个HashMap中 维护了四个属性 分别是 hash ,map ,key ,next
因为底层是数组加链表 数组的默认大小是16 每一个 用户put值的时候都会向这个数组进行添加 这里的添加算法就是 hash算法,一旦用户的数据向这个数组的某一项进行第二次辅助则这时就会使用链表的数据结构 一直向下延申 这个是jdk1.7 的做法
而到了jdk1.8以后 在延申的过程中一旦发现 延申的数量大于8个就会使用另一种的数据结构 红黑树以树的形式进行扩展进行添加
这时肯定会有人问刚刚一直往下延申这时 的横向延申不够怎么办 就是数组的长度不足怎么办 这时数组当然也是会扩容 以原理的二倍进行扩容相对来说效率比较高
HashMap 数组+链表+红黑树 在单线程的情况下简直是完美的
但在多线程的情况下是不完美的 会导致线程不安全
线程不安全 :
多线程操作一系列操作的时候和单线程操作表现的结果不致就说明线程非安全在 hashmap1.7中头插法hsah map是线程不安全的
这时我们的Hashtable 出现了
- hashtable不接受key 或者 value为空
- 线程安全
但是 是线程安全的 但在高并发 或者负载均衡的轮询等 效率太 低
ConcurrentHashMap
采用的是分段锁
在并发的概念中 有一个编程思想是CAS方式 采用无锁的方式 保证线程的安全 保证了原子性 效率比 synchronized 高
接下来我们对比一下HashMap 和 ConcurrentHashMap的put 方式
Hashmap 的put方法
ConcurrentHashMap的put 方法
点入到ConcurrentHashMap的put 方法
这时我们在ConcurrentHashMap集合中看到 好多volatile关键字
volatile
这个关键字 保证每次读到的都是主内存里面最新的值
- 扩容:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容
- ConcurrentHashMap默认将hash表分为16个桶,诸如get、put、remove等常用操作只锁住当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。