1. 程式人生 > >(轉帖)HashMap的讀寫併發 髒讀 髒資料

(轉帖)HashMap的讀寫併發 髒讀 髒資料

大家都知道HashMap不是執行緒安全的,但是大家的理解可能都不是十分準確。很顯然讀寫同一個key會導致不一致大家都能理解,但是如果讀寫一個不變的物件會有問題麼?看看下面的程式碼就明白了。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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中,有下面的原始碼:

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> --> 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中就會導致計算有偏移。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->1 static int indexFor(int h, int length) {
2         return h & (length-1);
3     }

很顯然在Map的容量(table.length,陣列的大小)有變化時就會導致此處計算偏移變化。這樣每次讀的時候就不一定能獲取到目標索引了。為了證明此猜想,我們改造下,變成以下的程式碼。

<!--<br /> <br /> Code highlighting produced by Actipro CodeHighlighter (freeware)<br /> http://www.CodeHighlighter.com/<br /> <br /> -->final Map<String, String> map = new HashMap<String, String>(10000);

執行多次結果總是輸出:

0
0
0
0
0
0
0
0
0
0

當然瞭如果只是讀,沒有寫肯定沒有併發的問題了。改換Hashtable或者ConcurrentHashMap肯定也是沒有問題了。

另外一篇文章.