Hashtable很少被用到,甚至在我们写代码的时候从来没有被用到,只有在背面试宝典的时候经常看到HahsMap和Hashtable的区别是HashMap是线程不安全的,Hashtable是线程安全的。那Hashtable为什么是线程安全的呢。下面来分析一下Hashtable的源码,看看它是怎么保证线程安全。
一.概括
Hashtable最大的特点是线程安全的,为了保证线程安全当然少不了关键字synchronized,可以说Hashtable在它提供给外部的方法前都加上了synchronized,Hashtable的实现原理和HashMap是一样的,所以Hashtable的方法和HashMap的方法基本相同,只是有些许细节可能不太一样。
二.Hashtable的基本数据结构和属性
1.Hashtable继承的抽象类和实现的接口
public class Hashtable<K,V>extends Dictionary<K,V>implements Map<K,V>, Cloneable, java.io.Serializable
其中Dictionary抽象类是key-value的映射表,规定一个key能找到相应的value值,但是key和value不能为空,这也导致了Hahstable和HahsMap的一个不同点就是HashMap的key和value可以为null,而Hashtable的key和value不能为空。
Map接口就定义了一些对map操作的基本方法。实现Cloneable接口,说明Hashtable能进行拷贝,至于是能对Hashtable进行深拷贝还是浅拷贝,从Hashtable的源码来看是浅拷贝。
2.Hashtable的属性和构造方法
/**
* The hash table data.
*/
private transient Entry<K,V>[] table;
/**
* The total number of entries in the hash table.
*/
private transient int count;
/**
* The table is rehashed when its size exceeds this threshold. (The
* value of this field is (int)(capacity * loadFactor).)
*
* @serial
*/
private int threshold;
/**
* The load factor for the hashtable.
*
* @serial
*/
private float loadFactor;
/**
* The number of times this Hashtable has been structurally modified
* Structural modifications are those that change the number of entries in
* the Hashtable or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the Hashtable fail-fast. (See ConcurrentModificationException).
*/
private transient int modCount = 0;
table:用于存储Entry对象的数组
count:Hashtable的大小,记录存储Hashtable存储元素的个数。
threshold:Hashtable存储元素个数的临界值,threshold = capacity*loadFactor;当存储元素的个数大于threshold的时候,会对Hashtable进行扩容;例如:当Hashtable的初始大小为20,加载因子为0.5,那么当Hashtable的存储元素个数大于等于10的时候,就会对Hashtable进行扩容。
loadFactor:加载因子。
modCount:记录修改?Hashtable的次数;用来实现“fail-fast”机制的(也就是快速失败)。所谓快速失败就是在并发集合中,其进行迭代操作时,若有其他线程对其进行结构性的修改,这时迭代器会立马感知到,并且立即抛出ConcurrentModificationException异常,而不是等到迭代完成之后才告诉你(你已经出错了)。
Hashtable的构造方法
/**
*带有以初始容量大小和加载因子为参数的构造方法
*/
public Hashtable(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal Load: "+loadFactor);
if (initialCapacity==0)
initialCapacity = 1;
this.loadFactor = loadFactor;
//初始容量的值其实是设定数组的大小
table = new Entry[initialCapacity];
//计算Hashtable的阀值
threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
//useAltHashing这个值只知道在rehash中用到,但不知道具体的作用是干什么的
useAltHashing = sun.misc.VM.isBooted() &&
(initialCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
}
/**
*只设置初始容量的话,加载因子是0.75
*/
public Hashtable(int initialCapacity) {
this(initialCapacity, 0.75f);
}
/**
* 可以看出Hashtable的默认的初始流量是11,加载因子是0.75
* 这点和HashMap有点不一样,HashMap的默认初始容量是16
*/
public Hashtable() {
this(11, 0.75f);
}
/**
* 将Mapt中的元素放入Hashtable中
*
*/
public Hashtable(Map<? extends K, ? extends V> t) {
//先设定Hashtable的大小和加载因子
this(Math.max(2*t.size(), 11), 0.75f);
putAll(t);
}
可以看出Hashtable和HashMap属性和构造方法基本是一样的,稍微有点不同的 Hashtable的默认初始容量是11,而HashMap的默认容量是16
3.HashTable的主要方法
public synchronized V get(Object key) {
Entry tab[] = table;
int hash = hash(key);
//用hash值对数组的长度进行取模运算,获取key值所对应的数组下标
int index = (hash & 0x7FFFFFFF) % tab.length;
//对以数组下标为index为表头的链表进行遍历
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return e.value;
}
}
return null;
}
public synchronized V put(K key, V value) {
// value不能为空,这也是Hashtable和HashMap的区别
if (value == null) {
throw new NullPointerException();
}
// Makes sure the key is not already in the hashtable.
Entry tab[] = table;
int hash = hash(key);
//hash值有可能是负数,0x7FFFFFFF的作用是让hash值变为正数,再对数组长度取模,算出在数组中的位置
int index = (hash & 0x7FFFFFFF) % tab.length;
//如果Hashtable中有key,key不变,替换key对应的value就可以了
for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
V old = e.value;
e.value = value;
return old;
}
}
modCount++;
//当Hashtable的大小大于等于阀值的时候,对Hashtable进行扩容
if (count >= threshold) {
// 对数组进行扩容
rehash();
tab = table;
hash = hash(key);
index = (hash & 0x7FFFFFFF) % tab.length;
}
// Creates the new entry.
Entry<K,V> e = tab[index];
//将封装了key-value的Entry对象放入数组中,如果数组这个位置还有值
//让新的对象的next属性指向原来的值,新的对象作为表头
tab[index] = new Entry<>(hash, key, value, e);
count++;
return null;
}
/**
*对Hashtable进行扩容
*/
protected void rehash() {
int oldCapacity = table.length;
Entry<K,V>[] oldMap = table;
// 新的长度是原来的2倍+1
int newCapacity = (oldCapacity << 1) + 1;
if (newCapacity - MAX_ARRAY_SIZE > 0) {
if (oldCapacity == MAX_ARRAY_SIZE)
// Keep running with MAX_ARRAY_SIZE buckets
return;
newCapacity = MAX_ARRAY_SIZE;
}
//新申明一个数组
Entry<K,V>[] newMap = new Entry[newCapacity];
modCount++;
threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
boolean currentAltHashing = useAltHashing;
useAltHashing = sun.misc.VM.isBooted() &&
(newCapacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD);
boolean rehash = currentAltHashing ^ useAltHashing;
table = newMap;
//将原来Hashtable中的元素复制到新的扩容的数组中,
for (int i = oldCapacity ; i-- > 0 ;) {
for (Entry<K,V> old = oldMap[i] ; old != null ; ) {
Entry<K,V> e = old;
old = old.next;
if (rehash) {
e.hash = hash(e.key);
}
int index = (e.hash & 0x7FFFFFFF) % newCapacity;
e.next = newMap[index];
newMap[index] = e;
}
}
}
在这些方法的的前面看到了关键字synchronized,这就是为什么Hashtable是线程安全的。
四.Hashtable和HashMap的不同点
1.Hashtable和HashMap所实现的接口和抽象类不一样 。
public class Hashtable<K,V>
extends Dictionary<K,V>
implements Map<K,V>, Cloneable, java.io.Serializable
public class HashMap<K,V>
extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable
2.Hashtable是线程安全的,这是它在每个方法前面加上了关键字synchronized,HashMap不是线程安全的,这是他们最大的不同点。但由于Hashtable在方法前面加上synchronized,如果多线程对Hashtable进行操作的话,得排队,不能同时操作。这样效率就比较低,有一个更好的选择是CurrentHashMap。CurrentHashMap加锁的粒度要比Hashtable要低,可以让多个线程同时操作。
3.HashMap中的key,value都可以为null,而Hashtable中的key和value都不允许为null。
4.扩容方式不一样,Hashtable是2*count+1,HashMap是2*count,而且HashMap中的数组长度永远都是2的整次幂。