1)put数据,覆盖导致丢失
假设2个线程:1)同时put数据 2)hash后都落在table[I] 3)按如下顺序执行createEntry代码段,最后map的数据如图状态1,thread2的put丢失。
timeline | thread1 | thread2 |
---|---|---|
1 | Entry<K,V> e = table[bucketIndex]; | |
2 | Entry<K,V> e = table[bucketIndex]; | |
3 | table[bucketIndex] = new Entry<>(hash, key, value, e); | |
4 | table[bucketIndex] = new Entry<>(hash, key, value, e); |
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry<K,V> e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
2)resize时,get返回null
假设2个线程:1)thread1 put数据触发resize 2)thead2 get数据,hash后都落在table[I] 3)且按如下顺序执行代码段,因为table[I]节点node.next==null,无法遍历链表,get返回null。
timeline | thread1 | thread2 |
---|---|---|
1 | while(null != e) {……} 只执行了一个循环,table变为table' |
|
2 | for (Entry<K,V> e = table[indexFor(hash, table.length)];e != null;e = e.next) {……} table[I]节点e,e.next == null |
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[I];
newTable[i] = e;
e = next;
}
}
}
/**
* Returns the entry associated with the specified key in the
* HashMap. Returns null if the HashMap contains no mapping
* for the key.
*/
final Entry<K,V> getEntry(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
for (Entry<K,V> e = table[indexFor(hash, table.length)];
e != null;
e = e.next) {
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
}
return null;
}
3)resize时,put导致数据丢失
假设2个线程:1)thread1 put触发resize 2)thread resize未完成时,thead2 put又触发resize 3)且按如下顺序执行代码段,最终table状态为newtable‘导致部分之前put的数据丢失。
timeline | thread1 | thread2 |
---|---|---|
1 | transfer(newTable, initHashSeedAsNeeded(newCapacity)); 如图newtable |
|
2 | transfer(newTable, initHashSeedAsNeeded(newCapacity)); 如图newtable’ |
|
3 | table = newTable; | |
4 | table = newTable; |
void resize(int newCapacity) {
Entry[] oldTable = table;
int oldCapacity = oldTable.length;
if (oldCapacity == MAXIMUM_CAPACITY) {
threshold = Integer.MAX_VALUE;
return;
}
Entry[] newTable = new Entry[newCapacity];
transfer(newTable, initHashSeedAsNeeded(newCapacity));
table = newTable;
threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
}
/**
* Transfers all entries from current table to newTable.
*/
void transfer(Entry[] newTable, boolean rehash) {
int newCapacity = newTable.length;
for (Entry<K,V> e : table) {
while(null != e) {
Entry<K,V> next = e.next;
if (rehash) {
e.hash = null == e.key ? 0 : hash(e.key);
}
int i = indexFor(e.hash, newCapacity);
e.next = newTable[I];
newTable[i] = e;
e = next;
}
}
}
4)resize死循环
可以看酷 壳 – CoolShell 的这篇文章:JAVA HASHMAP的死循环
注:
以上代码基于jdk1.7
IntelliJ IDEA 多线程debug