大家都知道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中,有下面的源码:
在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肯定也是没有问题了