hashmap:
底层是 单向链表,
非线程安全的、
可以存储空的键值对
HashMap在实现时对null做了特殊处理,将null的hashCode值定为了0
多线程情况下出现的问题:
HashMap 每次扩容的时候resize 都会重新计算下hash
a.丢失数据:
1.当多线程同时put值的时候,若发生hash碰撞,可能多个元素都落在链表的头部,从而造成元素覆盖(hashcode相同而 eques值不同的元素)
列如:线程A put一个元素a ,线程B put一个元素b,a,b 发生hansh碰撞,本应该在map是链表的形式存在,但是可能线程A 和线程B同时put到链表的第一个位置,从而后来者覆盖前者元素造成元素丢失。
b. put 造成链表形成闭环,get的时候出现死循环(jdk8已经解决该问题)
该情况是出现在多线线程操作map扩容时会发生
hashtable:
底层是 双向链表,
线程安全的,synchronized修饰put,get 等方法、
不可以存储空的键值对
对空值做空指针异常报错!(是运行时,才会报错)
public synchronized V put(K key, V value) {//向哈希表中添加键值对
return null;
}
ConcurrentHashMap
Hashtable的任何操作都会把整个表锁住,是阻塞的。好处是总能获取最实时的更新,比如说线程A调用putAll写入大量数据,期间线程B调用get,线程B就会被阻塞,直到线程A完成putAll,因此线程B肯定能获取到线程A写入的完整数据。坏处是所有调用都要排队,效率较低。
ConcurrentHashMap 是设计为非阻塞的,采用了分段锁技术,在更新时会局部锁住某部分数据,但不会把整个表都锁住。同步读取操作则是完全非阻塞的。好处是在保证合理的同步前提下,效率很高。坏处是严格来说读取操作不能保证反映最近的更新。例如线程A调用putAll写入大量数据,期间线程B调用get,则只能get到目前为止已经顺利插入的部分数据。
应该根据具体的应用场景选择合适的HashMap。