HashMap是非线程安全的,在多线程使用HashMap时一定要注意。具体详解可参考http://alex09.iteye.com/blog/539545
一.多线程put()
@Test
public void test1() throws InterruptedException {
final Map<Integer, String> map = new HashMap<Integer, String>();
for (int i = 0; i < 1000; i++) {
new Thread() {
public void run() {
for (int i = 0; i < 10; i++) {
map.put(i, i + "");
}
};
}.start();
}
Thread.sleep(10000);// 等线程执行完
List<Integer> keyList = new ArrayList<Integer>(map.keySet());
Collections.sort(keyList);
logger.debug("size=" + map.size() + ",keys=" + keyList);
}
执行结果如下:
2015-03-04 10:39:34.921 DEBUG ConcurrentHashMapTest:56 - size=17,keys=[0, 1, 1, 2, 3, 3, 4, 5, 6, 6, 7, 8, 9, 9]
可以看到数据发生了不一致。
甚至可能会出现死循环,参考《疫苗:Java HashMap的死循环》
二. ConcurrentModificationException异常
如果HashMap在迭代读取的过程中,发生结构性修改操作(增/删,不包括改),则会抛出ConcurrentModificationException异常。这是因为HashMap在进行结构性修改时会用modCount变量来记录操作个数,每次结构性修改时会+1。
/**
* The number of times this HashMap has been structurally modified
* Structural modifications are those that change the number of mappings in
* the HashMap or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the HashMap fail-fast. (See ConcurrentModificationException).
*/
transient int modCount;
例如remove()操作:
final Entry<K,V> removeEntryForKey(Object key) {
if (size == 0) {
return null;
}
int hash = (key == null) ? 0 : hash(key);
int i = indexFor(hash, table.length);
Entry<K,V> prev = table[i];
Entry<K,V> e = prev;
while (e != null) {
Entry<K,V> next = e.next;
Object k;
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k)))) {
modCount++;//删除时,结构性修改记录数+1
size--;
if (prev == e)
table[i] = next;
else
prev.next = next;
e.recordRemoval(this);
return e;
}
prev = e;
e = next;
}
return e;
}
还有put()操作:
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;//修改操作不会引起结构性修改,自然modCount也不会+1
}
}
modCount++;//新增时,结构性修改操作才会+1
addEntry(hash, key, value, i);
return null;
}
而在迭代开始的时候,会记下当前的modCount值给expectedModCount变量,如果在迭代的过程中HashMap发生了结构性修改操作则会抛出ConcurrentModificationException异常。
final Entry<K,V> nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry<K,V> e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index < t.length && (next = t[index++]) == null)
;
}
current = e;
return e;
}
再次强调一下,结构性修改操作是增和删,不包括修改。
2.1 增加
@Test
public void add() {
Map<Integer, String> map = new HashMap<Integer, String>();
for (int i = 0; i < 10; i++) {
map.put(i, i + "");
}
int i = 1;
Iterator<Integer> iter = map.keySet().iterator();
while (iter.hasNext()) {
int key = iter.next();
logger.debug("[" + (i++) + "]" + key);
map.put(key + 100, key + "");
}
}
执行上面的代码,会抛出ConcurrentModificationException异常。因为put()操作会增加新的<Key, Value>,导致结构性修改。
2.2 修改
@Test
public void add() {
Map<Integer, String> map = new HashMap<Integer, String>();
for (int i = 0; i < 10; i++) {
map.put(i, i + "");
}
int i = 1;
Iterator<Integer> iter = map.keySet().iterator();
while (iter.hasNext()) {
int key = iter.next();
logger.debug("[" + (i++) + "]" + key);
map.put(key, key + "-hello");
}
}
执行上面的代码,则可以正常执行。因为之前的key已存在,put()只是修改了value,并不会导致结构性修改。
2.2 删除
@Test
public void remove() {
Map<Integer, String> map = new HashMap<Integer, String>();
for (int i = 0; i < 10; i++) {
map.put(i, i + "");
}
int i = 1;
Iterator<Integer> iter = map.keySet().iterator();
while (iter.hasNext()) {
int key = iter.next();
logger.debug("[" + (i++) + "]" + key);
map.remove(1);//迭代过程中不允许修改
}
}
执行上面的代码,会抛出ConcurrentModificationException异常。因为remove()操作导致结构性修改。