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

来源:互联网 发布:高仿表 知乎 编辑:程序博客网 时间:2024/04/29 19:38

众所周知,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。//暂时正在研究源码。。。

0 0
原创粉丝点击