HashMap的jdk版本不同

关于HashMap,其实在jdk1.8和1.7版本之间的差别还是蛮大的,这几天在经过认真分析和理解之后,通过实验将学习成果进行展示。

首先提出一个问题:jdk1.7多线程环境下HashMap容易出现死循环?

public class HashMapTest {

     public static void main(String[] args) {
         HashMapThread thread0 = new HashMapThread();
         HashMapThread thread1 = new HashMapThread();
         HashMapThread thread2 = new HashMapThread();
         HashMapThread thread3 = new HashMapThread();
         HashMapThread thread4 = new HashMapThread();
         thread0.start();
         thread1.start();
         thread2.start();
         thread3.start();
         thread4.start();
     }
 }

 class HashMapThread extends Thread {
     private static AtomicInteger ai = new AtomicInteger();
     private static Map<Integer, Integer> map = new HashMap<>();

    @Override
     public void run() {
         while (ai.get() < 1000000) {
            map.put(ai.get(), ai.get());
             ai.incrementAndGet();
         }
     }
 }

请自行运行这段代码,模拟循环死锁现象。其实原理就是同时启动多个线程,不断进行put操作。

其原因就在于,jdk1.7版本中HashMap的扩容机制有问题,根源在transfer函数中,接下来阅读源码:

   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;
             }
         }
     }

从中知道,在将Table中的数据插入进NewTable中的过程中,采用头插法,会导致链表的顺序反转,也就是形成循环链表,造成死锁现象。
jdk1.8的改进
在此版本中采用尾部插入的方式,不会形成循环链表,但是仍在线程不安全,接下来看put操作。

  final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
 2                    boolean evict) {
 3         Node<K,V>[] tab; Node<K,V> p; int n, i;
 4         if ((tab = table) == null || (n = tab.length) == 0)
 5             n = (tab = resize()).length;
 6         if ((p = tab[i = (n - 1) & hash]) == null) // 如果没有hash碰撞则直接插入元素
 7             tab[i] = newNode(hash, key, value, null);
 8         else {
 9             Node<K,V> e; K k;
10             if (p.hash == hash &&
11                 ((k = p.key) == key || (key != null && key.equals(k))))
12                 e = p;
13             else if (p instanceof TreeNode)
14                 e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
15             else {
16                 for (int binCount = 0; ; ++binCount) {
17                     if ((e = p.next) == null) {
18                         p.next = newNode(hash, key, value, null);
19                         if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
20                             treeifyBin(tab, hash);
21                         break;
22                     }
23                     if (e.hash == hash &&
24                         ((k = e.key) == key || (key != null && key.equals(k))))
25                         break;
26                     p = e;
27                 }
28             }
29             if (e != null) { // existing mapping for key
30                 V oldValue = e.value;
31                 if (!onlyIfAbsent || oldValue == null)
32                     e.value = value;
33                 afterNodeAccess(e);
34                 return oldValue;
35             }
36         }
37         ++modCount;
38         if (++size > threshold)
39             resize();
40         afterNodeInsertion(evict);
41         return null;
42     }

这是jdk1.8中HashMap中put操作的主函数, 注意第6行代码,如果没有hash碰撞则会直接插入元素。如果线程A和线程B同时进行put操作,刚好这两条不同的数据hash值一样,并且该位置数据为null,所以这线程A、B都会进入第6行代码中。假设一种情况,线程A进入后还未进行数据插入时挂起,而线程B正常执行,从而正常插入数据,然后线程A获取CPU时间片,此时线程A不用再进行hash判断了,问题出现:线程A会把线程B插入的数据给覆盖,发生线程不安全。
另外区别:再新版本中链表长度达到一定长度时,会自动转化为红黑树。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值