首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

HashMap由于多线程 null

2012-11-16 
HashMap因为多线程 nullHashMap因为多线程未同步时导致put进的元素get出来为null的分析当你明明put进了一

HashMap因为多线程 null
HashMap因为多线程未同步时导致put进的元素get出来为null的分析

当你明明put进了一对非null key-value进了HashMap,某个时候你再用这个key去取的时候却发现value为null,再次取的时候却又没问题,都知道是HashMap的非线程安全特性引起的,分析具体原因如下:

Java代码
public V get(Object key) { 
        if (key == null) 
            return getForNullKey(); 
        int hash = hash(key.hashCode()); 
 
        // indexFor方法取得key在table数组中的索引,table数组中的元素是一个链表结构,遍历链表,取得对应key的value 
        for (Entry<K, V> e = table[indexFor(hash, table.length)]; e != null; e = e.next) { 
            Object k; 
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 
                return e.value; 
        } 
        return null; 
    } 


再看看put方法:

Java代码
public V put(K key, V value) { 
        if (key == null) 
            return putForNullKey(value); 
        int hash = hash(key.hashCode()); 
        int i = indexFor(hash, table.length); 
        for (Entry<K, V> e = table[i]; e != null; e = e.next) { 
            Object k; 
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
                V oldValue = e.value; 
                e.value = value; 
                e.recordAccess(this); 
                return oldValue; 
            } 
        } 
 
        modCount++; 
        // 若之前没有put进该key,则调用该方法 
        addEntry(hash, key, value, i); 
        return null; 
    } 



再看看addEntry里面的实现:

Java代码
void addEntry(int hash, K key, V value, int bucketIndex) { 
        Entry<K, V> e = table[bucketIndex]; 
        table[bucketIndex] = new Entry<K, V>(hash, key, value, e); 
        if (size++ >= threshold) 
            resize(2 * table.length); 
    } 
里面有一个if块,当map中元素的个数(确切的说是元素的个数-1)大于或等于容量与加载因子的积时,里面的resize是就会被执行到的,继续resize方法:


Java代码
void resize(int newCapacity) { 
        Entry[] oldTable = table; 
        int oldCapacity = oldTable.length; 
        if (oldCapacity == MAXIMUM_CAPACITY) { 
            threshold = Integer.MAX_VALUE; 
            return; 
        } 
 
        Entry[] newTable = new Entry[newCapacity]; 
        transfer(newTable); 
        table = newTable; 
        threshold = (int) (newCapacity * loadFactor); 
    } 


resize里面重新new一个Entry数组,其容量就是旧容量的2倍,这时候,需要重新根据hash方法将旧数组分布到新的数组中,也就是其中的transfer方法:

Java代码
void transfer(Entry[] newTable) { 
        Entry[] src = table; 
        int newCapacity = newTable.length; 
        for (int j = 0; j < src.length; j++) { 
            Entry<K, V> e = src[j]; 
            if (e != null) { 
                src[j] = null; 
                do { 
                    Entry<K, V> next = e.next; 
                    int i = indexFor(e.hash, newCapacity); 
                    e.next = newTable[i]; 
                    newTable[i] = e; 
                    e = next; 
                } while (e != null); 
            } 
        } 
    } 
在这个方法里,将旧数组赋值给src,遍历src,当src的元素非null时,就将src中的该元素置null,即将旧数组中的元素置null了,也就是这一句:

Java代码
if (e != null) { 
        src[j] = null; 
此时若有get方法访问这个key,它取得的还是旧数组,当然就取不到其对应的value了。


下面,我们重现一下场景:

Java代码
import java.util.HashMap; 
import java.util.Map; 
public class TestHashMap { 
    public static void main(String[] args) { 
        final Map<String, String> map = new HashMap<String, String>(4, 0.5f); 
         
        new Thread(){ 
            public void run() { 
                while(true) {  
                    System.out.println(map.get("name1")); 
                    try { 
                        Thread.sleep(1000); 
                    } catch (InterruptedException e) { 
                        e.printStackTrace(); 
                    } 
                } 
            } 
        }.start(); 
        for(int i=0; i<3; i++) { 
            map.put("name" + i, "value" + i); 
        } 
    } 

Debug上面这段程序,在map.put处设置断点,然后跟进put方法中,当i=2的时候就会发生resize操作,在transfer将元素置null处停留片刻,此时线程打印的值就变成null了。

其它可能由未同步HashMap导致的问题:
1、多线程put后可能导致get死循环(主要问题在于put的时候transfer方法循环将旧数组中的链表移动到新数组)
2、多线程put的时候可能导致元素丢失(主要问题出在addEntry方法的new Entry<K,V>(hash, key, value, e),如果两个线程都同时取得了e,则他们下一个元素都是e,然后赋值给table元素的时候有一个成功有一个丢失)



总结:HashMap在并发程序中会产生许多微妙的问题,难以从表层找到原因。所以使用HashMap出现了违反直觉的现象,那么可能就是并发导致的了

热点排行