Java程序员都曾被问到的一个问题是:
- 为什么HashMap是线程不安全的?
- 为什么ConcurrentHashMap是线程安全的?
为什么HashMap是线程不安全的?
Fail-Fast 机制
- 如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。
- 看源码的时候我们会看到,在ArrayList,LinkedList,HashMap的内部,有一个int类型的modCount对象,对上述集合内容的修改都将增加这个值。modCount会在迭代器Iterator中使用,在迭代器的构造函数中,有这么一行代码,expectedModCount = modCount。在nextEntity和remove方法的调用过程中,如果modCount != expectedModCount,抛出ConcurrentModificationException异常。
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public void remove() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
死循环
- HashMap中有两个重要的属性,一个是容量,一个是加载因子,容量必须是2的n次方,这是为了保证key hash之后的值可以均匀的分散在数组里。当前结点个数等于容量*加载因子之后,需要进行扩容,扩展为原来的二倍,这个过程称为rehash。
- 因为hashmap是头插入法,新的结点插入在数组的头结点。当线程1执行rehash到newTable[i] = e时,线程1被挂起,此时线程2开始执行rehash并完成,这样会导致结点的循环引用问题。
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
// table是老数组
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
}
}
}