HashMap为什么线程不安全以及解决方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Yixiong_Cao/article/details/50253563

众所周知,hashmap线程不安全而hashtable线程安全。最近在看并发编程就稍微研究了一下。先看一段JAVAAPI中对hashmap的介绍:

*注意,此实现不是同步的。如果多个线程同时访问此映射,而其中至少一个线程从结构上修改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关系的操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问,如下所示:
Map m = Collections.synchronizedMap(new HashMap(…));*

hashmap的底层是一个链表散列(数组与链表的结合体)。

1.
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);
}

hashmap在调用put方法时会调用上面方法,假若现在同时有A线程和B线程同时获得了同一个头节点并对其进行修改,这样A写入头节点之后B也写入头节点则会将A的操作覆盖

2.

final Entry<K,V> removeEntryForKey(Object key) {  
        int hash = (key == null) ? 0 : hash(key.hashCode());  
        int i = indexFor(hash, table.length);  
        Entry<K,V> prev = table[i];  
        Entry<K,V> e = prev;  

        while (e != null) {  
            Entry<K,V> next = e.next;  
            Object k;  
            if (e.hash == hash &&  
                ((k = e.key) == key || (key != null && key.equals(k)))) {  
                modCount++;  
                size--;  
                if (prev == e)  
                    table[i] = next;  
                else  
                    prev.next = next;  
                e.recordRemoval(this);  
                return e;  
            }  
            prev = e;  
            e = next;  
        }  

        return e;  
    }  

这上面是删除操作,同样的如果一个头节点的元素同时被两个线程删除,则又会造成混乱。

3.
`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);  
}  `

以上是hashmap的增加容量的操作,如果同时A,B两个线程都要执行resize()操作,A线程已经进行在原基础的扩容,B线程也在原基础上扩容,这样就会造成线程不安全。

下面写一个例子来验证一下
import java.util.HashMap;
import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

public class hashMap {
    static void dos(){
        HashMap<Long,String> ss=new HashMap<Long,String>();
        final AtomicInteger in=new AtomicInteger(0);
        ss.put(0L, "ssssssss");
        for(int i=0;i<200;i++){
            new Thread(new Runnable() {         
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    ss.put(System.nanoTime()+new Random().nextLong(),"ssssssssssss");
                    String s=ss.get(0L);
                    if(s==null){
                        in.incrementAndGet();
                    }
                }
            }).start();

        }
        System.out.println(in);

    }
    public static void main(String[] args) {
        for(int i=0;i<10;i++){
            dos();

    }

}
}

结果并不都是0

这里写图片描述

但是如果将hashmap的初始大小设的大一些就不会出现这种情况。

下来研究一下如何使hashmap线程安全

http://flyfoxs.iteye.com/blog/2100120
//两种方式让hashmap线程安全

1.第一种办法api中已经有指出
如果不存在这样的对象,则应该使用 Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止对映射进行意外的不同步访问,如下所示:
Map m = Collections.synchronizedMap(new HashMap(…));
即使用Collections.synchronizedMap()来返回一个新的map,但是这个返回的是不是hashmap,而是一个map的实现

第二种办法是改写hashmap。//暂时正在研究源码。。。

展开阅读全文

没有更多推荐了,返回首页