ConcurrentHashMap引入了分段锁的概念。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问,能够实现真正的并发访问。
首先先来看一些基本的属性:
//初始容量,这个值指的是table的初始容量
static final int DEFAULT_INITIAL_CAPACITY = 16;
//并发数,其实也就是Segments数组的大小,默认是16,初始化后不可以改变。
static final int DEFAULT_CONCURRENCY_LEVEL = 16;
//负载因子,这个负载因子是给每个 Segment 内部使用的。
static final float DEFAULT_LOAD_FACTOR = 0.75f;
在ConcurrentHashMap的源码中可以看到以下属性:
- Segment<K,V>[] 类型的segments数组
- HashEntry<K,V>[] 类型的table数组
- 链表结构的HashEntry<K,V>
//segments数组
final Segment<K,V>[] segments;
//table数组
transient volatile HashEntry<K,V>[] table;
//链表结构的HashEntry<K,V>
static final class HashEntry<K,V> {
final int hash;
final K key;
volatile V value;
volatile HashEntry<K,V> next;
//HashEntry的构造函数
HashEntry(int hash, K key, V value, HashEntry<K,V> next) {
this.hash = hash;
this.key = key;
this.value = value;
this.next = next;
}
// Segment的构造函数
Segment(float lf, int threshold, HashEntry<K,V>[] tab) {
this.loadFactor = lf;
this.threshold = threshold;
this.table = tab;
}
根据上述源码我画出了ConcurrentHashMap的内部结构图:
我们还可以看到如下源码:
static final class Segment<K,V> extends ReentrantLock implements Serializable
由此我们可以看出,Segment继承了ReentrantLock,表明每个segment都可以当做一个可重入锁。每个segment中的数据需要同步操作的话都是使用每个segment容器对象自身的锁来实现。
HashTable和ConcurrentHashMap线程安全保证机制的不同:
- HashTable容器使用synchronized来保证线程安全,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,在线程竞争激烈的情况下HashTable的效率非常低下。因为当一个线程访问HashTable的同步方法时,其他线程访问HashTable的同步方法时,可能会进入阻塞或轮询状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
- ConcurrentHashMap允许多个修改操作并发进行,内部使用Segment来表示这些不同的部分,每个段其实就是一个小的hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。
HashMap、HashTable和ConcurrentHashMap区别:
- HashMap
(1).线程不安全。
(2).继承自 AbstractMap实现了 Map, Cloneable, Serializable接口。
(3).支持序列化。
(4).key、value都可以为null。
(5).一次hash找到指定的key然后遍历entry链表。 - HashTable
(1).线程安全 (所有的方法都加了sychronized实现)。
(2).继承自Dictionary,实现了Map、Cloneable、Java.io.Serializable接口。
(3).不支持序列化。
(4).key、value都不可以为null。
(5).一次hash找到指定的key然后遍历entry链表。 - concurrentHashMap
(1).线程安全(使用ReenTrantLock(可重入锁) 分段锁技术实现)。
(2).继承自AbstractMap,实现了ConcurrentMap, Serializable接口。
(3).支持序列化。
(4).key、value都不可以为null。
(5).put和 get 两次Hash到达指定的HashEntry,第一次hash到达Segment,第二次到达Segment里面的table,然后在遍历HashEntry链表。