在阅读本文章之前,最好是了解一下我们为什么要使用ConcurrentHashMap,观看下面一篇文章:
JDK1.7中ConcurrentHashMap源码了解前准备
我们现在就直接来看ConcurrentHashMap它底层源码到底做了什么事情来保证线程安全的:
分段锁机制
之前观看过我解读HashMap源码的同学应该知道,HashMap的对象实际是存储在Map接口里面的Entry对象中的,但是在ConcurrentHashMap中,我们在外层还包裹了一层Segment数组,用来存储Segment对象,而这个Segment类他继承了RentrantLock并且里面有一个HashEntry数组属性。
而我们点开这个HashEntry对象就可以发现,它其实就是相当于HashMap里面的Entry对象:
也就是说我们可以大概的画出ConcurrentHashMap的结构示意图:
这种结构的好处
我们在put的时候,首先也会根据传入的key先生成一个hash值以及对应的数组下标,但是我们现在有两个数组,一个是外层的Segment数组,一个是Segment对象中的HashEntry数组;
单线程的情况下
1.我们计算出来第一个index下标,是外层Segment数组的下标,并且生成Segment对象;
2.然后调用当前Segment对象的lock方法进行加锁(segment.lock);
3.然后再计算在HashEntry数组中的index下标
4.再生成Entry对象,放到HashEntry数组中去,如果形成了链表则插入到链表中;
多线程的情况下
如果这个时候有两个线程同时进行put
1.同时计算出相同的hash值,并且计算出在Segment数组中的下标值也是相同的;
2.两个线程都生成了相同的segment对象并且调用它的lock方法进行加锁,但是这两个线程只有一个线程能够加到锁,加到锁的线程就正常执行,执行完成之后释放锁,而没有加到锁的线程就只有等到拿到锁之后再进行后续的执行;
上面说的情况是计算出的segment数组下标相同的情况,如果下标值不同呢?这个时候ConcurrentHashMap就体现出来了;
如果两个线程计算出的下标值不同,那么拿到的segment对象就是不同的,那么两者调用lock方法加锁,就不存在锁的竞争,那么这两个线程就可以同时并发添加了,因此性能就更高了;
构造方法
我们创建ConcurrentHashMap的时候,如果是使用无参构造器创建,那么它在无参构造器中也会将默认的初始化参数进行赋值,然后传入到重载方法中去:
我们先来看这些参数代表什么意思,跟HashMap里面相同的参数我就不再细讲了,例如initialCapacity初始化容量,用来决定扩容阈值的loadFactor负载因子;
这里的initialCapacity指的是所有segment对象里面的所有Entry对象的总和
ConcurrencyLevel(默认16):并发级别(最多支持多少个线程同时执行,跟外层Segment数组容量的大小有关系,并且是固定的,它在初始化之后就不会改变了)
但是并没有直接用这个并发级别来指定Segment数组的容量,它在内部做了一些处理:
我们可以看到它的容量使用了一个ssize来指定,那么我们就去看看这个ssize做了什么事情;