大家都知道HashMap不是線程安全的,但是大家的理解可能都不是十分準(zhǔn)確。很顯然讀寫同一個(gè)key會(huì)導(dǎo)致不一致大家都能理解,但是如果讀寫一個(gè)不變的對(duì)象會(huì)有問題么?看看下面的代碼就明白了。
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
結(jié)果一定會(huì)輸出0么?結(jié)果卻不一定。比如某一次的結(jié)果是:
0
3
0
0
0
0
9
0
9
0
查看了源碼,其實(shí)出現(xiàn)這個(gè)問題是因?yàn)镠ashMap在擴(kuò)容是導(dǎo)致了重新進(jìn)行hash計(jì)算。
在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 }
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中就會(huì)導(dǎo)致計(jì)算有偏移。
1 static int indexFor(int h, int length) {
2 return h & (length-1);
3 }
很顯然在Map的容量(table.length,數(shù)組的大小)有變化時(shí)就會(huì)導(dǎo)致此處計(jì)算偏移變化。這樣每次讀的時(shí)候就不一定能獲取到目標(biāo)索引了。為了證明此猜想,我們改造下,變成以下的代碼。
final Map<String, String> map = new HashMap<String, String>(10000);
執(zhí)行多次結(jié)果總是輸出:
0
0
0
0
0
0
0
0
0
0
當(dāng)然了如果只是讀,沒有寫肯定沒有并發(fā)的問題了。改換Hashtable或者ConcurrentHashMap肯定也是沒有問題了。
這里有一篇完整分析HashMap原理的文章。 __ by xylz 2010.07.20