集合---HashMap的不安全

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的升级)         

 https://www.cnblogs.com/wen-he/p/11496050.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值