HashTable
特点:底层数据结构:数组+链表(内部类Entry构成的数组)
线程安全:synchronized关键字修饰(锁的颗粒度较大;JVM锁);效率低
初始化:初始化大小为11,负载因子为0.75
存储以及扩容: Key和Value存储Null时,会抛出NullPointerException
扩容:当实际大小大于扩容阈值=初始化大小*负载因子时;会扩大2倍加1
结合源码分析:
数据结构:数组+链表
/**
* The hash table data.
*/
private transient Entry<?,?>[] table;
...
private static class Entry<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Entry<K,V> next;
...
线程安全:依赖于synchronized
public synchronized V put(K key, V value) {
// Make sure the value is not null
if (value == null) { // VALUE值不为NULL
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry<?,?> tab[] = table;
int hash = key.hashCode(); // key值为Null会导致空指针
int index = (hash & 0x7FFFFFFF) % tab.length;
@SuppressWarnings("unchecked")
Entry<K,V> entry = (Entry<K,V>)tab[index];
for(; entry != null ; entry = entry.next) {
if ((entry.hash == hash) && entry.key.equals(key)) {
V old = entry.value;
entry.value = value;
return old;
}
}
addEntry(hash, key, value, index);
return null;
}
以put方法为例同时put方法中也有对KEY/VALUE值为NULL的处理
Hash值的运算:直接使用KEY的HashCode,然后最大int值与运算再对数组长度(默认11)取模。
private void addEntry(int hash, K key, V value, int index) {
modCount++;
Entry<?,?> tab[] = table;
if (count >= threshold) {
// Rehash the table if the threshold is exceeded
rehash();
tab = table;
hash = key.hashCode(); // 直接使用KEY的HashCode
index = (hash & 0x7FFFFFFF) % tab.length; //最大int值与运算再对数组长度(默认11)取模
}
// Creates the new entry.
@SuppressWarnings("unchecked")
Entry<K,V> e = (Entry<K,V>) tab[index];
tab[index] = new Entry<>(hash, key, value, e);
count++;
}
总结:
应对面试还需学习;但是实际生产中HashTable使用较少;无需线程安全,HashMap(1.8还进行了相关优化);需线程安全ConcurrentHashMap,效率更高,而且JDK1.8还做了相关优化。
ConcuurentHashMap
特点:
数据结构:JDK1.7 数组+链表 内部类(Entry数组) +分段锁(保证线程安全) 内部类:Entry,Segment(分段锁)
JDK1.8 数组+链表+红黑树 + (CAS机制+Synchronized【保证线程安全】) 内部类:HashEntry
其他方面ConcurrentHashMap很像HashMap
源码分析
JDK1.7
/**
* Segments are specialized versions of hash tables. This
* subclasses from ReentrantLock opportunistically, just to
* simplify some locking and avoid separate construction.
*/
static final class Segment<K,V> extends ReentrantLock
implements Serializable {
...
transient volatile HashEntry<K,V>[] table;
...
说明:ReentrantLock 实现了每一个Segment的线程同步(原子性);同时volatile HashEntry保证HashEntry的可见性和有序性。
JDK1.8
// Unsafe mechanics CAS机制相关的Unsafe类
private static final sun.misc.Unsafe U;
/**
* Initializes table, using the size recorded in sizeCtl.
*/
private final Node<K,V>[] initTable() {
Node<K,V>[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
// CAS机制
try {
if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt;
sc = n - (n >>> 2);
}
} finally {
sizeCtl = sc;
}
break;
}
}
return tab;
}
1:使用Unsafe类(CAS机制)来实现数据修改的原子性;2:get,put等方法使用Synchronized关键字来实现县城安全。
这里理解为什么使用了Synchronized关键字了,还要使用CAS机制来实现线程安全?
我的理解是,Synchronized修饰的是存储,获取方法;CAS机制针对的是集合大小的改变,JDK1.8中使用一个volatile类型的变量baseCount记录元素的个数,当插入新数据或则删除数据时,会通过addCount()方法更新baseCount
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
// U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)
// 该方法判断本地存储的baseCount是否与缓存中一致,若一直才容许修改
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
总结:
JDK1.7到JDK1.8,ConcurrentHashMap的锁的粒度进行了细化,由原来的分段锁(多个Segment组成,同一个Segment中同步),变为基于数组下表进行加锁;极大的提高了性能。
注:ReentrantLock ,后续线程安全会详细讲到。AQS也会在后续的博客中进行更新。
unsafe类以及与其相关的CAS机制,在线程安全中也会详细讲到。