★
1.hashMap存在的问题
public static void main(String[] args) throws InterruptedException {
Map<Integer,Integer> map=new HashMap<>();
Thread t1 = new Thread() {
public void run() {
for (int i = 0; i < 50000; i++) {
map.put(new Integer(i), i);
}
System.out.println("t1 over");
}
};
Thread t2 = new Thread() {
public void run() {
for (int i = 0; i < 50000; i++) {
map.put(new Integer(i), i);
}
System.out.println("t2 over");
}
};
t1.start();t2.start();
t1.join();t2.join();
System.out.println(map.size());
}
运行这个代码,就会产生问题:
1.一个是会产生数据丢失,普通的集合在多线程下面,都会产生数据丢失的问题,这个是由于线程私有内存造成的,数据没有同步,
2.其次还会产生死循环问题,这个是JDK7的问题,这个存在于resize()操作,当新的空间分配之后,我们需要把数据从旧的table中,迁移到新的table,这个时候需要调用transfer()方法
void transfer(Entry[] newTable)
{
Entry[] src = table;
int newCapacity = newTable.length;
//下面这段代码的意思是:
// 从OldTable里摘一个元素出来,然后放到NewTable中
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); //遍历链表
}
}
}
待resize()的结构
如果并发情况下进行rehash,如果一个线程在do-while中挂起了,那么可能就会出现下面的情况:
线程一,挂住(刚执行完next=e.next,那么这个时候next=7),
线程二,rehash()完成
线程线程二不管了,线程一回来工作,继续执行上面的do-while代码内容,
newTable[i]=e 那么table[3]就指向了Key3
e=next 那么e=key7
这个时候e不等于null,再一次循环。
保存e.next 因为线程2已经rehash完成,所以key7又指向key3,所以next保存了key3
newTable[i]=e table[3]指向key7,
e=next e又指向key3
再一次循环:table[3]指向key3,e.next终于为空,但是key7还是指向key3,所以就形成了循环!
产生了循环链表,那么在get()某个元素,可能就会出现死循环
总结:
1.在jdk1.7中,在多线程环境下,扩容时会造成环形链或数据丢失。
2.在jdk1.8中,在多线程环境下,会发生数据覆盖的情况
解决办法:
1.实验HashTable,它的方法添加了synchronized关键字,线程安全。但是锁太重
2.将HashMap包装,给它的方法添加synchronized,同样锁太重了。
3.使用ConcurrentHashMap
jdk8如何解决循环链问题:
此处老值和新值说明:老值的意思是之前的hash值,后扩容后的hash值相同,则称为老值,扩容后重新计算出来的hash值不同,则称为新值
else { // preserve order
Node<K,V> loHead = null, loTail = null; //双指针 老值
Node<K,V> hiHead = null, hiTail = null; //新值
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {//老值链表 通过while生成桶的头尾节点
if (loTail == null)
loHead = e; //保存头部
else
loTail.next = e; //向后走
loTail = e;
}
else { //新值链表
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead; //移动整个链表
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}
}
它使用了两个指针来分别指向头节点和尾节点,而且还保证了元素原本的顺序。它是等链表循环结束后,才给数组赋值,jdk7是一个一个插入,而jdk8是整个桶擦插入
参考连接:
https://blog.csdn.net/paincupid/article/details/51241783(有图)
https://www.jianshu.com/p/e1c020d37c6a (jdk8的升级)