HashMap HashTable ConcurrentHashMap

1. Hashtable 和 HashMap


(1)区别,这两个类主要有如下几方面的不一样:
Hashtable和HashMap都实现了Map接口,可是Hashtable的实现是基于Dictionary抽象类。
 
在HashMap中,null能够做为键,这样的键只有一个;能够有一个或多个键所对应的值为null。 
当get()方法返回null值时,便可以表示 HashMap中没有该键,也能够表示该键所对应的值为null。
 所以,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
 而在Hashtable中,不管是key仍是value都不能为null 。
 
   这两个类最大的不一样在于:
(1)Hashtable是线程安全的,它的方法是同步了的,能够直接用在多线程环境中。
(2)而HashMap则不是线程安全的。在多线程环境中,须要手动实现同步机制。





所以,在Collections类中提供了一个方法返回一个同步版本的HashMap用于多线程的环境:
Java代码
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {   
 return new SynchronizedMap<K,V>(m);   
 }  
该方法返回的是一个SynchronizedMap 的实例。
SynchronizedMap类是定义在Collections中的一个静态内部类。
它实现了Map接口,并对其中的每个方法实现,经过synchronized 关键字进行了同步控制。
 
2. 潜在的线程安全问题
上面提到Collections为HashMap提供了一个并发版本SynchronizedMap。
这个版本中的方法都进行了同步,可是这并不等于这个类就必定是线程安全的。
在某些时候会出现一些意想不到的结果。
以下面这段代码:
Java代码
// shm是SynchronizedMap的一个实例   
if(shm.containsKey('key')){   
        shm.remove(key);   
}  
 这段代码用于从map中删除一个元素以前判断是否存在这个元素。
 这里的containsKey和reomve方法都是同步的,可是整段代码却不是。
 
 考虑这么一个使用场景:
线程A执行了containsKey方法返回true,准备执行remove操做;
这时另外一个线程B开始执行,一样执行了containsKey方法返回true,并接着执行了remove操做;
而后线程A接着执行remove操做时发现此时已经没有这个元素了。
要保证这段代码按咱们的意愿工做,一个办法就是对这段代码进行同步控制,可是这么作付出的代价太大。
 
在进行迭代时这个问题更改明显。Map集合共提供了三种方式来分别返回键、值、键值对的集合:
Java代码
Set<K> keySet();   
  
Collection<V> values();   
  
Set<Map.Entry<K,V>> entrySet();  


 在这三个方法的基础上,咱们通常经过以下方式访问Map的元素:
Java代码
Iterator keys = map.keySet().iterator();   
  
while(keys.hasNext()){   
        map.get(keys.next());   
}  
 
在这里,有一个地方须要注意的是:获得的keySet和迭代器都是Map中元素的一个“视图”,而不是“副本” 。
问题也就出如今这里,当一个线程正在迭代Map中的元素时,另外一个线程可能正在修改其中的元素。
此时,在迭代元素时就可能会抛出 ConcurrentModificationException异常。


为了解决这个问题一般有两种方法,
(1)一是直接返回元素的副本,而不是视图。这个能够经过
集合类的 toArray() 方法实现,可是建立副本的方式效率比以前有所下降,
特别是在元素不少的状况下;
(2)另外一种方法就是在迭代的时候锁住整个集合,这样的话效率就更低了。


3. 更好的选择:ConcurrentHashMap


java5中新增了ConcurrentMap接口和它的一个实现类ConcurrentHashMap。


ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不一样的锁机制。
Hashtable中采用的锁机制是一次锁住整个hash表,从而同一时刻只能由一个线程对其进行操做;
而ConcurrentHashMap中则是一次锁住一个桶。
ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等经常使用操做只锁当前须要用到的桶。
这样,原来只能一个线程进入,如今却能同时有16个写线程执行,并发性能的提高是显而易见的。
 
上面说到的16个线程指的是写线程,而读操做大部分时候都不须要用到锁。只有在size等操做时才须要锁住整个hash表。
 
在迭代方面,ConcurrentHashMap使用了一种不一样的迭代方式。
在这种迭代方式中,当iterator被建立后集合再发生改变就再也不是抛出ConcurrentModificationException,
取而代之的是  在改变时new新的数据从而不影响原有的数据 。
iterator完成后再将头指针替换为新的数据 。
这样iterator线程可使用原来老的数据。java

而写线程也能够并发的完成改变。web

相关文章
相关标签/搜索