一、多線程put后get為null
源碼定位
1 void transfer(Entry[] newTable) { 2 Entry[] src = table; 3 int newCapacity = newTable.length; 4 for ( int j = 0 ; j < src.length; j++) { 5 Entry e = src[j]; 6 if (e != null ) { 7 src[j] = null ;//將table[j]設置為null,並發訪問到 原table返回的就是null 8 do { 9 Entry next = e.next; 10 int i = indexFor(e.hash, newCapacity); 11 e.next = newTable[i]; 12 newTable[i] = e; 13 e = next; 14 } while (e != null ); 15 } 16 } 17 }
分析:線程1將src[j] = null;即將table[j] = null;因為代碼第二行定義了Entry[] src = table;即src和table是對同一對象的引用。
這時切換到線程2,線程2此時若正在調用get(key)方法:
1 public V get(Object key) { 2 3 if (key == null) 4 5 return getForNullKey(); 6 7 int hash = hash(key.hashCode()); 8 9 // indexFor方法取得key在table數組中的索引,table數組中的元素是一個鏈表結構,遍歷鏈表,取得對應key的value 10 11 for (Entry e = table[indexFor(hash, table.length)]; e != null; e = e.next) { 12 13 Object k; 14 15 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 16 17 return e.value; 18 19 } 20 21 return null; 22 }
若get(key)中key經hash和indexFor()計算后正好落到table[j]上,則此時取到的Entry為null(第11行),直接跳出for循環,來到第21行,return null,違反了錯覺。
二、多線程put的時候可能導致元素丟失
源碼定位
1 void addEntry( int hash, K key, V value, int bucketIndex) 2 { 3 Entry<K,V> e = table[bucketIndex]; 4 table[bucketIndex] = new Entry<K,V>(hash, key, value, e);//這里線程1和線程2同時獲取e,執行后必然有一個丟失 5 if (size++ >= threshold) 6 resize( 2 * table.length); 7 }
問題出在第4行table[bucketIndex] = new Entry<K,V>(hash,key,value,e);如果兩個線程同時都取得了e,則他們下一個元素都是e,然后賦值給table元素的時候有一個成功有一個丟失(先賦值的丟失)。
