public class HashMap extends AbstractMap<K,V> impement Map<K,V>,Coloneable,Serializable{
可以看出HashMap实现了Map接口。其里面的方法都是非线程安全的,且不支持并发操作。
对于HashMap主要看的是get/put方法实现,其在jdk1.7,及1.8在解决哈希冲突的上有所不同。
一、Java7 HashMap

在HashMap中定义的成员变量:
capacity:当前数组容量,始终保持 2^n,可以扩容,扩容后数组大小为当前的 2 倍。
loadFactor:负载因子,默认为 0.75。
threshold:扩容的阈值,等于 capacity * loadFactor,当容量超过这个值时,数组将扩容。
transient int modCount; //HashMap修改次数,这个值用于和expectedModCount期望修改次数比较。
1、put方法解析:
public V put(K key, V value) {
//1.当插入第一个元素时,需要创建并初始化指定大小的数组
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
二、java7 ConcurrentHashMap
在java7 下ConcurrentHashMap结构如下:
现在我们已经说完了 put 过程和 get 过程,我们可以看到 get 过程中是没有加锁的,那自然我们就需要去考虑并发问题。
添加节点的操作 put 和删除节点的操作 remove 都是要加 segment 上的独占锁的,所以它们之间自然不会有问题,我们需要考虑的问题就是 get 的时候在同一个 segment 中发生了 put 或 remove 操作。
put 操作的线程安全性。
初始化槽,这个我们之前就说过了,使用了 CAS 来初始化 Segment 中的数组。
添加节点到链表的操作是插入到表头的,所以,如果这个时候 get 操作在链表遍历的过程已经到了中间,是不会影响的。当然,另一个并发问题就是 get 操作在 put 之后,需要保证刚刚插入表头的节点被读取,这个依赖于 setEntryAt 方法中使用的 UNSAFE.putOrderedObject。
扩容。扩容是新创建了数组,然后进行迁移数据,最后面将 newTable 设置给属性 table。所以,如果 get 操作此时也在进行,那么也没关系,如果 get 先行,那么就是在旧的 table 上做查询操作;而 put 先行,那么 put 操作的可见性保证就是 table 使用了 volatile 关键字。
remove 操作的线程安全性。
get 操作需要遍历链表,但是 remove 操作会”破坏”链表。
如果 remove 破坏的节点 get 操作已经过去了,那么这里不存在任何问题。
如果 remove 先破坏了一个节点,分两种情况考虑。 1、如果此节点是头结点,那么需要将头结点的 next 设置为数组该位置的元素,table 虽然使用了 volatile 修饰,但是 volatile 并不能提供数组内部操作的可见性保证,所以源码中使用了 UNSAFE 来操作数组,请看方法 setEntryAt。2、如果要删除的节点不是头结点,它会将要删除节点的后继节点接到前驱节点中,这里的并发保证就是 next 属性是 volatile 的。
参考:https://javadoop.com/post/hashmap