CuncurrentHashMap 的重要知识点
jdk1.8与jdk1.7对比
-
jdk1.7中,ConcurrentHashMap使用分段锁的机制,将整个哈希表分为若干个Segment,每个Segment相当于一个小的哈希表,可以独立加锁和扩容。这样可以提高并发访问的效率,但是也增加了内存开销和复杂度。
-
jdk1.8中,ConcurrentHashMap取消了Segment的概念,采用了CAS和synchronized相结合的方式来实现并发安全。具体来说,对于读操作,不需要加锁,只需要用CAS来保证原子性;对于写操作,只需要对链表或红黑树的头节点加锁,而不是整个桶或Segment。这样可以减少锁的粒度和竞争,提高性能和空间利用率。
-
jdk1.8中,ConcurrentHashMap还引入了一些新的特性,如红黑树的优化,当链表长度超过一定阈值时,会转换为红黑树,以提高查找效率;扩容的优化,使用多线程并发地进行扩容,并且通过特殊的节点类型(ForwardingNode)来保证扩容过程中仍然可以访问旧表中的元素;聚合操作的支持,如forEach, reduce, search等方法,可以利用多线程并行地处理元素,并提供自定义的函数接口。
总结:
JDK1.8中的ConcurrentHashMap相比于JDK1.7中的ConcurrentHashMap有以下优点:
- 锁的粒度更细,减少了锁竞争的概率。
- 数据结构更优化,提高了查找和插入的性能。
- 使用CAS操作来减少锁的使用,提高了并发效率。
额外特点
- ConcurrentHashMap是一个支持高并发和高效率的哈希表,它继承自AbstractMap类,实现了ConcurrentMap和Serializable接口。
- ConcurrentHashMap内部使用一个名为table的Node数组来存储键值对,Node是一个单向链表节点,包含四个字段:hash, key, val, next。hash是键的哈希值,key和val是键值对,next是指向下一个节点的引用。
- ConcurrentHashMap还使用一个名为sizeCtl的volatile变量来控制数组的初始化和扩容。当sizeCtl为负数时,表示数组正在初始化或扩容;当sizeCtl为正数时,表示数组已经初始化,并且表示下一次扩容时的阈值;当sizeCtl为0时,表示数组还未初始化。
- ConcurrentHashMap使用多种特殊的节点类型来实现不同的功能。例如:
- ForwardingNode:用于扩容时,表示当前节点已经被迁移到新表中,并且存储了新表的引用。
- TreeBin:用于链表转换为红黑树时,表示当前节点是一个树形结构,并且存储了根节点和锁对象的引用。
- TreeNode:用于红黑树中,继承自Node类,并且增加了一些字段和方法来实现树形操作。
- ReservationNode:用于计算元素个数时,表示当前节点是一个占位符,并且不存储任何键值对。
- ConcurrentHashMap支持多种并发操作,如put, get, remove, replace等。这些操作都利用了CAS和synchronized来保证原子性和可见性。具体来说:
- 对于get操作,不需要加锁,只需要遍历链表或红黑树,直到找到匹配的键或者到达末尾。如果遇到ForwardingNode,则跳转到新表中继续查找。
- 对于put操作,在确定插入位置后,先尝试用CAS来插入节点,如果成功则返回;如果失败,则说明有其他线程正在修改该位置,此时需要加锁,然后再次检查该位置的状态,如果仍然可以插入,则插入节点;如果已经有相同的键存在,则更新值;如果该位置已经变成了树形结构,则调用树形操作来插入节点。如果插入后发现元素个数超过了阈值,则尝试触发扩容。
- 对于remove操作,在确定删除位置后,先尝试用CAS来删除节点,如果成功则返回;如果失败,则说明有其他线程正在修改该位置,此时需要加锁,然后再次检查该位置的状态,如果仍然可以删除,则删除节点;如果该位置已经变成了树形结构,则调用树形操作来删除节点。如果删除后发现树形结构的节点个数少于一定阈值,则尝试转换为链表。
- 对于replace操作,在确定替换位置后,先尝试用CAS来替换值,如果成功则返回;如果失败,则说明有其他线程正在修改该位置,此时需要加锁,然后再次检查该位置的状态,如果仍然可以替换,则替换值;如果该位置已经变成了树形结构,则调用树形操作来替换值。
- ConcurrentHashMap还支持一些聚合操作,如forEach, reduce, search等。这些操作都利用了多线程并行地处理元素,并且提供了自定义的函数接口。例如:
- 对于forEach操作,可以指定一个BiConsumer函数来对每个键值对进行消费操作,并且可以指定一个long参数来控制并行度。
- 对于reduce操作,可以指定一个BiFunction函数来对每个键值对进行转换操作,并且指定一个BinaryOperator函数来对转换后的结果进行合并操作,并且可以指定一个long参数来控制并行度。
- 对于search操作,可以指定一个BiFunction函数来对每个键值对进行搜索操作,并且返回一个满足条件的结果,并且可以指定一个long参数来控制并行度。
jdk1.8中ConcurrentHashMap和其他map集合类的区别
- 与HashMap相比,ConcurrentHashMap是线程安全的,而HashMap是非线程安全的。ConcurrentHashMap使用CAS和synchronized来保证并发安全,而HashMap没有任何同步措施。ConcurrentHashMap不允许存储null键或null值,而HashMap允许存储一个null键和任意个null值。ConcurrentHashMap支持一些聚合操作,而HashMap不支持。
- 与Hashtable相比,ConcurrentHashMap是高效的,并发安全的哈希表,而Hashtable是低效的,并发安全的哈希表。ConcurrentHashMap使用CAS和synchronized来保证并发安全,并且减少锁的粒度和竞争,而Hashtable使用synchronized来保证并发安全,并且锁住整个哈希表。ConcurrentHashMap不允许存储null键或null值,而Hashtable也不允许存储null键或null值。ConcurrentHashMap支持一些聚合操作,而Hashtable不支持。
- 与LinkedHashMap相比,ConcurrentHashMap是无序的,并发安全的哈希表,而LinkedHashMap是有序的,非线程安全的哈希表。ConcurrentHashMap使用CAS和synchronized来保证并发安全,而LinkedHashMap没有任何同步措施。ConcurrentHashMap不允许存储null键或null值,而LinkedHashMap允许存储一个null键和任意个null值。ConcurrentHashMap支持一些聚合操作,而LinkedHashMap不支持。LinkedHashMap还支持一种基于访问顺序的淘汰机制,而ConcurrentHashMap不支持。
- 与TreeMap相比,ConcurrentHashMap是无序的,并发安全的哈希表,而TreeMap是有序的,非线程安全