标签:就是 lan 存储 exit map 出现 并发 fail 死循环
HashMap其实并不是线程安全的,在高并发的情况下,会产生并发引起的问题:
比如:
下面逐个分析下出现上述情况的原因:
HashMap进行存储时,如果size超过(当前最大容量*负载因子)时候会发生resize,首先看一下resize源代码:
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方法是真正执行rehash的操作,容易在高并发时发生问题
transfer(newTable);
table = newTable;
threshold = (int)(newCapacity * loadFactor);
}
而这段代码中又调用了transfer()方法,而这个方法实现的机制就是将每个链表转化到新链表,并且链表中的位置发生反转,而这在多线程情况下是很容易造成链表回路,从而发生死循环,我们看一下他的源代码:
void transfer(Entry[] newTable) {
Entry[] src = table;
int newCapacity = newTable.length;
for (int j = 0; j < src.length; j++) {
Entry<K,V> e = src[j];
if (e != null) {
src[j] = null;
do {
Entry<K,V> next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
}
}
}
HashMap死循环演示:
假如有两个线程P1、P2,以及table[]某个节点链表为 a->b->null(a、b是HashMap的Entry节点,保存着Key-Value键值对的值)
P1先执行,执行完"Entry<K,V> next = e.next;"代码后,P1发生阻塞或者其他情况不再执行下去,此时e=a,next=b
transfer(newTable); //P1阻塞在transfer方法中,没有执行到下边对 table 和 threshold 重新赋值的操作 table = newTable; threshold = (int)(newCapacity * loadFactor);
一个线程利用迭代器迭代时,另一个线程做插入删除操作,造成迭代的fast-fail。
public class TestFailFast {
private static final String USER_NAME_PREFIX = "User-";
// Key: User Name, Value: User Age
private static Map<String, Integer> userMap = new HashMap<>();
// ThreadA 用于向HashMap添加元素
static class ThreadA implements Runnable {
@Override
public void run() {
System.out.println("ThreadA starts to add user.");
for (int i = 1; i < 100000; i++) {
userMap.put(USER_NAME_PREFIX+i, i%100);
}
System.out.println("ThreadA done.");
}
}
// ThreadB 用于遍历HashMap中元素输出
static class ThreadB implements Runnable {
@Override
public void run() {
System.out.println("ThreadB starts to iterate.");
for (Map.Entry<String, Integer> user : userMap.entrySet()) {
System.out.println("UserName=" + user.getKey()
+ ", UserAge=" + user.getValue());
}
System.out.println("ThreadB done.");
}
}
public static void main(String[] args) throws InterruptedException {
Thread threadA = new Thread(new ThreadA());
Thread threadB = new Thread(new ThreadB());
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.exit(0);
}
}
运行结果:抛出ConcurrentModificationException
ThreadA starts to add user.
ThreadB starts to iterate.
Exception in thread "Thread-1" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at concurrent.TestFailFast$ThreadB.run(TestFailFast.java:33)
at java.lang.Thread.run(Thread.java:748)
ThreadA done.
HashMap并非线程安全,所以在多线程情况下,应该首先考虑用ConcurrentHashMap,避免悲剧的发生。
https://blog.csdn.net/chenxuegui1234/article/details/39646041
https://blog.csdn.net/u011716215/article/details/78601916
标签:就是 lan 存储 exit map 出现 并发 fail 死循环
原文地址:https://www.cnblogs.com/lanqiu5ge/p/9606645.html