ConcurrentHashMap的put方法执行过程:
-
定位 Node 节点:首先,使用键(key)的哈希值计算出在哈希表中的索引位置,然后尝试定位到对应的 Node 节点。
-
检查 Node 状态:在找到 Node 节点后,检查该节点的状态。如果节点已经存在,并且它的哈希值、键值与要插入的键值相匹配,那么直接返回旧值。
-
处理并发:如果 Node 节点不存在,或者存在但与要插入的键值不匹配,此时可能会遇到并发情况。为了保证线程安全,
ConcurrentHashMap
使用了synchronized
关键字对链表或者红黑树进行加锁,以防止多个线程同时修改同一个桶(bucket)。 -
链表或红黑树:在 JDK 1.8 中,
ConcurrentHashMap
使用链表和红黑树两种结构存储哈希桶中的元素。当链表长度超过一定阈值(默认为 8)时,链表会转换成红黑树以提高性能。 -
插入元素:在链表或红黑树中插入新的键值对。如果插入成功,返回
null
,表示之前没有这个键的值。 -
处理扩容:在某些情况下,如果
ConcurrentHashMap
需要扩容,put
方法会触发扩容操作。扩容过程中,旧的哈希表将被复制到一个新的更大的哈希表中,同时新的键值对也会被插入到新的哈希表中。 -
返回结果:最后,
put
方法返回与新插入键关联的旧值,如果之前没有这个键的值,则返回null
。
Node
是基本的存储单元,用于存储键值对。以下是 Node
节点的一些关键特性和结构:
ConcurrentHashMap中的Node节点
-
继承关系:
Node
类继承自AbstractMap.SimpleImmutableEntry
类,这意味着Node
包含了键(key)和值(value)的基本结构。 -
静态内部类:
Node
是ConcurrentHashMap
的一个静态内部类,这意味着它与外部类的实例紧密相关。 -
哈希值:每个
Node
实例都有一个与键相关联的哈希值(hash),这个哈希值用于确定该节点在哈希表中的位置。 -
next 字段:
Node
类有一个next
字段,用于指向下一个Node
实例。这允许ConcurrentHashMap
使用链表结构处理哈希冲突。 -
volatile 修饰:
next
字段被volatile
关键字修饰,以确保在多线程环境下对该字段的修改能够立即对其他线程可见,从而保证内存的可见性。 -
TreeBin 转换:当链表的元素数量超过一定的阈值(默认为 8)时,链表将被转换成红黑树(
TreeBin
)。TreeBin
是Node
的子类,它除了包含next
字段外,还包含了红黑树所需的其他字段,如parent
(父节点)、left
(左子节点)、right
(右子节点)等。 -
value 与 key:
Node
类通过继承AbstractMap.SimpleImmutableEntry
类,拥有getKey()
和getValue()
方法,用于获取节点的键和值。同时,也提供了setValue()
方法,用于更新节点的值。 -
构造函数:
Node
类提供了一个构造函数,用于创建一个新的键值对节点。构造函数接收键、值和哈希值作为参数。 -
懒加载:在 JDK 1.8 中,如果
ConcurrentHashMap
初始化时未指定初始容量,它会延迟初始化,直到实际需要存储元素时才进行。 -
清理:
ConcurrentHashMap
在某些情况下可能会进行清理操作,例如在remove
方法中,如果某个Node
被删除,next
字段将被设置为null
,以帮助垃圾回收器回收不再使用的对象。
综上所述,Node
节点是 JDK 1.8 中 ConcurrentHashMap
的基本存储单元,它通过继承和字段设计,实现了线程安全的键值对存储和哈希表的高效操作。