HashMap为什么是线程不安全的?

hashmap线程不安全在哪里?

    博客分类:
  • j2se
  • java

大家都知道HashMap不是线程安全的,但是大家的理解可能都不是十分准确。很显然读写同一个key会导致不一致大家都能理解,但是如果读写一个不变的对象会有问题么?看看下面的代码就明白了。

 1  import  java.util.HashMap;
 2  import  java.util.Map;
 3  import  java.util.Random;
 4  import  java.util.concurrent.ExecutorService;
 5  import  java.util.concurrent.Executors;
 6  import  java.util.concurrent.TimeUnit;
 7  import  java.util.concurrent.atomic.AtomicInteger;
 8 
 9  public   class  HashMapTest2 {
10       static   void  doit()  throws  Exception{
11           final   int  count  =   200 ;
12           final  AtomicInteger checkNum  =   new  AtomicInteger( 0 );
13          ExecutorService newFixedThreadPool  =  Executors.newFixedThreadPool( 100 );
14           //
15           final  Map < Long, String >  map  =   new  HashMap < Long, String > ();
16          map.put( 0L " www.imxylz.cn " );
17           // map.put(1L, "www.imxylz.cn");
18           for  ( int  j  =   0 ; j  <  count; j ++ ) {
19              newFixedThreadPool.submit( new  Runnable() {
20                   public   void  run() {
21                      map.put(System.nanoTime() + new  Random().nextLong(),  " www.imxylz.cn " );
22                      String obj  =  map.get( 0L );
23                       if  (obj  ==   null ) {
24                          checkNum.incrementAndGet();
25                      }
26                  }
27              });
28          }
29          newFixedThreadPool.awaitTermination( 1 , TimeUnit.SECONDS);
30          newFixedThreadPool.shutdown();
31          
32          System.out.println(checkNum.get());
33      }
34      
35       public   static   void  main(String[] args)  throws  Exception{
36           for ( int  i = 0 ;i < 10 ;i ++ ) {
37              doit();
38              Thread.sleep( 500L );
39          }
40      }
41  }
42 

结果一定会输出0么?结果却不一定。比如某一次的结果是:

 

0
3
0
0
0
0
9
0
9
0

 

查看了源码,其实出现这个问题是因为HashMap在扩容是导致了重新进行hash计算。

在HashMap中,有下面的源码:

 

 1       public  V get(Object key) {
 2           if  (key  ==   null )
 3               return  getForNullKey();
 4           int  hash  =  hash(key.hashCode());
 5           for  (Entry < K,V >  e  =  table[indexFor(hash, table.length)];
 6               e  !=   null ;
 7               e  =  e.next) {
 8              Object k;
 9               if  (e.hash  ==  hash  &&  ((k  =  e.key)  ==  key  ||  key.equals(k)))
10                   return  e.value;
11          }
12           return   null ;
13      }

 

在indexOf中就会导致计算有偏移。

1  static   int  indexFor( int  h,  int  length) {
2           return  h  &  (length - 1 );
3      }

 敲打我的理解:假如本来0L 的 hash 是000,长度10 ,别人在扩容,这边到indexFor了,依然是000,但长度20,就找不出来原来的index,相应的值可能也就没了

不过假如所有的线程都恢复平静之后,我自己再去根据这个key 0L去拿,hash是根据key 和 20算的,算位置的时候也是这个hash 和20去算

自然是对应的了,所有应该是可以出来数据的,而我认为不一致现象只是短暂的!害羞

很显然在Map的容量(table.length,数组的大小)有变化时就会导致此处计算偏移变化。这样每次读的时候就不一定能获取到目标索引了。为了证明此猜想,我们改造下,变成以下的代码。

final  Map < String, String >  map  =   new  HashMap < String, String > ( 10000 );

执行多次结果总是输出:

 

0
0
0
0
0
0
0
0
0
0

 

当然了如果只是读,没有写肯定没有并发的问题了。改换Hashtable或者ConcurrentHashMap肯定也是没有问题了

你可能感兴趣的:(HashMap为什么是线程不安全的?)