currentHashMap的介绍

ConcurrentHashMap是线程安全并且高效的一种容器,我们就需要研究一下ConcurrentHashMap为什么既能够保证线程安全,又可以保证高效的操作。

为什么使用ConcurrentHashMap,我们就需要和HashMap以及HashTable进行比较?

HashMap是线程不安全的,在多线程的情况下,HashMap的操作会引起死循环,导致CPU的占有量达到100%,所以在并发的情况下,我们不会使用HashMap

死锁原因

在 HashMap 扩容的时候会调用 resize() 方法,就是这里的并发操作容易在一个桶上形成环形链表;这样当获取一个不存在的 key 时,计算出的 index 正好是环形链表的下标就会出现死循环。

面试官再问currentHashMap,就将这篇文章甩给他_currentHashMap

HashTable其中使用synchronize来保证线程安全,即当有一个线程拥有锁的时候,其他的线程都会进入阻塞或者轮询状态,这样会使得效率越来越低。

ConcurrentHashMapMap的锁分段技术可以有效的提高并发访问率
HashTable访问效率低下的原因,就是因为所有的线程在竞争同一把锁。

如果容器中有多把锁,不同的锁锁定不同的位置,这样线程间就不会存在锁的竞争,这样就可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术将数据一段一段的存储,为每一段都配一把锁,当一个线程只是占用其中的一个数据段时,其他段的数据也能被其他线程访问。

jdk7 的currentHashMap

JDK1.7ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。

采用Segment(分段锁)来减少锁的粒度,ConcurrentHashMap中的分段锁称为Segment,它即类似于HashMap的结构,即内部拥有一个Entry数组,数组中的每个元素又是一个链表,同时又是一个ReentrantLockSegment继承了ReentrantLock)。

currentHashMap内部结构

ConcurrentHashMap使用分段锁技术,将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。如下图是ConcurrentHashMap的内部结构图:

面试官再问currentHashMap,就将这篇文章甩给他_currentHashMap_02
Segment默认是 16,按理说最多同时支持 16个线程并发读写,但是是操作不同的 Segment,初始化时也可以指定 Segment数量,每一个 Segment都会有一把锁,保证线程安全。

该结构的优劣势

  1. 坏处是这一种结构的带来的副作用是Hash的过程要比普通的HashMap要长。

  2. 好处是写操作的时候可以只对元素所在的Segment进行加锁即可,不会影响到其他的Segment,这样,在最理想的情况下,ConcurrentHashMap可以最高同时支持Segment数量大小的写操作(刚好这些写操作都非常平均地分布在所有的Segment上)。

所以,通过这一种结构,ConcurrentHashMap的并发能力可以大大的提高。

JDK1.8的currentHashMap

JDK1.8currentHashMap参考了1.8HashMap的实现方式,采用了数组+链表+红黑树的实现方式,其中大量的使用CAS操作.CAS(compare and swap)的缩写,也就是我们说的比较交换。

CAS是一种基于锁的操作,而且是乐观锁。java的锁中分为乐观锁和悲观锁。悲观锁是指将资源锁住,等待当前占用锁的线程释放掉锁,另一个线程才能够获取线程.乐观锁是通过某种方式不加锁,比如说添加version字段来获取数据。

CAS操作包含三个操作数(内存位置,预期的原值,和新值)。如果内存的值和预期的原值是一致的,那么就转化为新值。CAS是通过不断的循环来获取新值的,如果线程中的值被另一个线程修改了,那么A线程就需要自旋,到下次循环才有可能执行。

JDK8中彻底放弃了Segment转而采用的是Node,其设计思想也不再是JDK1.7中的分段锁思想。

Node:保存keyvaluekeyhash值的数据结构。其中valuenext都用volatile修饰,保证并发的可见性。

Java8ConcurrentHashMap结构基本上和Java8HashMap一样,不过保证线程安全性。在JDK8ConcurrentHashMap的结构,由于引入了红黑树,使得ConcurrentHashMap的实现非常复杂。

我们都知道,红黑树是一种性能非常好的二叉查找树,其查找性能为O(logN),早期完全采用链表结构时Map的查找时间复杂度为O(N)JDK8ConcurrentHashMap在链表的长度大于某个阈值的时候会将链表转换成红黑树进一步提高其查找性能。

面试官再问currentHashMap,就将这篇文章甩给他_currentHashMap_03jdk8中currentHashMap原理图

总结

看完了整个 HashMapConcurrentHashMap 在 1.7 和 1.8 中不同的实现方式相信大家对他们的理解应该会更加到位。其实这块也是面试的重点内容,通常的套路是:

  • 谈谈你理解的 HashMap,讲讲其中的 get put 过程。

  • 1.8 做了什么优化?

  • 是线程安全的嘛?

  • 不安全会导致哪些问题?

  • 如何解决?有没有线程安全的并发容器?

  • ConcurrentHashMap 是如何实现的?1.7、1.8 实现有何不同?为什么这么做?

这一串问题相信大家仔细看完都能怼回面试官。

 

面试官再问currentHashMap,就将这篇文章甩给他_currentHashMap_04