谈谈HashMap线程不安全的体现

  然后该执行 key(3)的 next 节点 key(7)了:

  现在的 e 节点是 key(7),首先执行 Entry<k,v> next = e.next ,那么 next 就是 key(3)了

  执行 e.next = newTable[i] ,于是key(7) 的 next 就成了 key(3)

  执行 newTable[i] = e ,那么线程1的新 Hash 表第一个元素变成了 key(7)

  执行 e = next ,将 e 指向 next,所以新的 e 是 key(3)

  这时候的状态图为:

  然后又该执行 key(7)的 next 节点 key(3)了:

  现在的 e 节点是 key(3),首先执行 Entry<k,v> next = e.next ,那么 next 就是 null

  执行 e.next = newTable[i] ,于是key(3) 的 next 就成了 key(7)

  执行 newTable[i] = e ,那么线程1的新 Hash 表第一个元素变成了 key(3)

  执行 e = next ,将 e 指向 next,所以新的 e 是 key(7)

  这时候的状态如图所示:

  很明显,环形链表出现了!!当然,现在还没有事情,因为下一个节点是 null,所以transfer() 就完成了,等 put() 的其余过程搞定后,HashMap 的底层实现就是线程1的新 Hash 表了。

  2. fail-fast

  如果在使用迭代器的过程中有其他线程修改了map,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。

  这个异常意在提醒开发者及早意识到线程安全问题,具体原因请查看 ConcurrentModificationException的原因以及解决措施

  顺便再记录一个HashMap的问题:

  为什么String, Interger这样的wrapper类适合作为键?String, Interger这样的wrapper类作为HashMap的键是再适合不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个field声明成final就能保证hashCode是不变的,那么请这么做吧。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能。