面试技术架构java集合相关

java并发编程之ConcurrentHashMap

2017-01-19  本文已影响2533人  miaoLoveCode

引言

ConcurrentHashMap是线程安全并且高效的HashMap,在并发编程中经常可见它的使用,在开始分析它的高并发实现机制前,先讲讲废话,看看它是如何被引入jdk的。

为什么引入ConcurrentHashMap?

  1. HashMap线程不安全,它的线程不安全主要发生在put等对HashEntry有直接写操作的地方:


    HashMap线程不安全操作源码示例

    从put操作的源码不难看出,线程不安全主要可能发生在这两个地方:

  1. Hashtable线程安全,但是效率低下:


    Hashtable源码示例.png

    从Hashtable示例的源码可以看出,Hashtable是用synchronized关键字来保证线程安全的,由于synchronized的机制是在同一时刻只能有一个线程操作,其他的线程阻塞或者轮询等待,在线程竞争激烈的情况下,这种方式的效率会非常的低下。

注:小小的多嘴一句,Hashtable扩容的时候newSize = 2 * oldSize + 1,这个是常识性的点,但是由于整个jdk源码封装比较好,而且Hashtable效率低下,使用较少,貌似好多程序员都不太知道这一点。

ConcurrentHashMap的为什么高效?
Hashtable低效主要是因为所有访问Hashtable的线程都争夺一把锁。如果容器有很多把锁,每一把锁控制容器中的一部分数据,那么当多个线程访问容器里的不同部分的数据时,线程之前就不会存在锁的竞争,这样就可以有效的提高并发的访问效率。这也正是ConcurrentHashMap使用的分段锁技术。将ConcurrentHashMap容器的数据分段存储,每一段数据分配一个Segment(锁),当线程占用其中一个Segment时,其他线程可正常访问其他段数据。

ConcurrentHashMap实现分析

在分析ConcurrentHashMap的源码之前先来看看它的结构:

ConcurrentHashMap类图

从类图可以看出:ConcurrentHashMap由Segment和HashEntry组成。

由此可以得出ConcurrentHashMap的结构图如下:

ConcurrentHashMap结构图
初始化ConcurrentHashMap
ConcurrentHashMap构造方法

可以看出,ConcurrentHashMap的构造方法都调用了public ConcurrentHashMap(int initialCapacity, float loadFactor, int concurrencyLevel),初始化部分都由它来完成,我们来看一看它是怎么来初始化ConcurrentHashMap的。

ConcurrentHashMap初始化具体实现
整个初始化是通过参数initialCapacity,loadFactor和concurrencyLevel来初始化segmentShift(段偏移量)、segmentMask(段掩码)和segment数组。

ConcurrentHashMap初始化具体实现

注:concurrencyLevel的最大值是65535,那么,ssize的最大值就为65536,对应到二进制就是16位。

Segment定位

注:为什么需要再散列?
再散列的目的是为了减少冲突,让元素可以近似均匀的分布在不同的Segment上,从而提升存储效率。如果hash算法不好,最差的情况是所有的元素都在一个Segment中,这时候hash表将退化成链表,查询插入的时间复杂度都会从理想的o(1)退化成o(n^2),同时,分段锁也会失去存在的意义。

ConcurrentHashMap相关操作实现分析

主要分析ConcurrentHashMap常用的三个操作:get/put/size的具体实现。

比起Hashtable,ConcurrentHashMap的get操作高效之处在于整个get操作不需要加锁。如果不加锁,ConcurrentHashMap的get操作是如何做到线程安全的呢?原因是volatile,所有的value都定义成了volatile类型,volatile可以保证线程之间的可见性,这也是用volatile替换锁的经典应用场景。


HashEntry value定义
上一篇 下一篇

猜你喜欢

热点阅读