精华内容
下载资源
问答
  • 解决HashMap并发问题
    千次阅读
    2016-03-30 23:10:01


    1 如上图:在HashMap的官方文档中已经指出,为了避免并发问题,需要使用Map m = Collections.synchronizedMap(new HashMap(...));的方式来实例化HashMap。否则可能会导致非同步访问。


    2 使用ConcurrentHashMap。


    更多相关内容
  • HashMap并发问题

    千次阅读 2018-08-19 11:16:05
    在JDK 1.8中,Map相关类的实现发生了很大变化,引入了红黑树的概念,本篇以JDK 1.7中的实现方式讲解HashMap并发问题,以方便理解。 说明 JDK 1.8和JDK 1.7中,Map相关类的大体实现思想变化不大,知识...

    之前在讲HashMap的时候提到过HashMap线程不安全,在并发环境下会发生死锁问题,将导致CPU占用率接近100%。其实死锁的说法并不很贴切,应该说是一种死循环。在JDK 1.8中,Map相关类的实现发生了很大变化,引入了红黑树的概念,本篇以JDK 1.7中的实现方式讲解HashMap的并发问题,以方便理解。

    说明

    JDK 1.8和JDK 1.7中,Map相关类的大体实现思想变化不大,知识引入了红黑树的概念,使得类中增加了生成红黑树的方法,相关的一些重要方法,例如添加元素,重置集合大小等方法也做出相应改变。
    上述这些变化主要是为了提高集合的查询效率,思想还是原来的思想,因此JDK 1.7中的源代码更能方便理解。

    HashMap并发死锁问题

    该问题的成因涉及到四个方法,最初的起因是调用put()方法,跟着方法走一遍:
    put()方法源码(只给出核心部分):

    public V put(K key, V value)
    {
        ......
        //计算Hash值
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        //如果该key已存在,则替换掉旧的value
        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++;
        //该key不存在,需要增加一个结点
        addEntry(hash, key, value, i);
        return null;
    }
    

    当key不存在时,调用addEntry()方法添加新节点。方法源码如下:

    void addEntry(int hash, K key, V value, int bucketIndex)
    {
        Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        //查看当前的size是否超过了阈值threshold,如果超过,需要resize
        if (size++ >= threshold)
            resize(2 * table.length);
    }
    

    resize()方法就是产生并发死锁的原因

    void resize(int newCapacity)
    {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        ......
        //创建一个新的Hash Table
        Entry[] newTable = new Entry[newCapacity];
        //将Old Hash Table上的数据迁移到New Hash Table上
        transfer(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
    }
    

    resize()方法的本质就是创建新的Entry数组,将原Map中的元素重新计算位置,加入到新的Map中。虽然死锁的成因是扩充时调用resize()方法,但真正的产生是发生在倒数第三行的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;
                    //计算节点在新的Map中的位置
                    int i = indexFor(e.hash, newCapacity);
                    e.next = newTable[i];
                    newTable[i] = e;
                    e = next;
                } while (e != null);
            }
        }
    }
    

    大体过程如下(摘自网上大神的博客)

     

    transfe过程

    假设hash算法就是简单的用key mod Entry数组的长度。这里一定注意e和next的指向,当并发resize()时,这两个指针对于死锁产生起着至关重要的作用。根据方法执行情况,原Map中的链表元素在新的Map中将顺序颠倒,如上图所示,经过一次resize()后key为7的节点排在了key为3的节点之前。

    do {
      Entry<K,V> next = e.next;
      //计算节点在新的Map中的位置
      int i = indexFor(e.hash, newCapacity);
      e.next = newTable[i];
      newTable[i] = e;
       e = next;
    } while (e != null);
    

    再次黏贴这段代码就是强调这个do while循环就是产生死锁的罪魁祸首。下面模拟死锁产生的过程。
    注意,并非所有情况下都会产生死锁,这也需要线程之间的默契配合,怎么讲呢,如图所示:

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

    线程一的记录状态

    此时线程一,e指向key为3的节点,next指向key为7的节点。这点很重要,记下来。去执行线程二。
    假设线程二正常执行,结束后的状态如下:

    线程二正常执行

    此时线程一被唤醒,线程一的工作空间里,e和next指向的元素依旧是key为3和7的节点。线程一开始执行。

    先是执行 newTalbe[i] = e。
    然后是e = next,导致了e指向了key(7)。
    而下一次循环的next = e.next导致了next指向了key(3)。
    

    线程一执行

    目前还没发生问题,线程一接着工作。把key(7)摘下来,放到newTable[i]的第一个,然后把e和next往下移。

    线程一执行

    e.next = newTable[i] 导致 key(3).next 指向了 key(7)。注意:此时的key(7).next 已经指向了key(3), 环形链表就这样出现了。

    死锁产生
    这个过程不好理解,最好多读几遍,当产生带环链表后,如果调用get()方法,将会陷入死循环,CPU占用将达到100%。解决这一问题的方式有多种。比较low的可以使用HashTable和调用Collections工具类的synchronizedMap()方法达到线程安全的目的。但由于synchronized是串行执行,在访问量很大的情况下效率很低,不推荐使用。
    另外一种方式就是使用JUC包下的ConcurrentHashMap类,这个类很好的解决了多线程环境下的并发问题

    展开全文
  • 今天不知为什么服务器卡死了把所有线程的堆栈打印出来是这样的java.util.HashMap.get..................com.labox.common.net.ReceiveWorker.runjava.util.concurrent.ThreadPoolExecutor$Worker.runTaskjava.util....

    今天不知为什么服务器卡死了

    把所有线程的堆栈打印出来是这样的

    java.util.HashMap.get

    ..................

    com.labox.common.net.ReceiveWorker.run

    java.util.concurrent.ThreadPoolExecutor$Worker.runTask

    java.util.concurrent.ThreadPoolExecutor$Worker.run

    java.lang.Thread.run

    所有线程都是卡死在这里了

    后来也想不出为什么就用ConcurrentHashMap代替了

    突然在ibm上发现一编文章是这写的

    JR33299: 6.2.0.1: java.util.HashMap.get() causes high CPU use after migration

    0818b9ca8b590ca3270a3433284dd417.png

    < ******** PASTE MAIN BODY CONTENT HERE ******** >

    Downloadable files

    Abstract

    After migration of a WebSphere InterChange Server repository and deployment of the migrated artifacts to WebSphere Process Server, the migrated content causes high CPU use.

    Download Description

    JR33299 resolves the following problem:

    ERROR DESCRIPTION:

    After migration of a WebSphere InterChange Server repository and deployment of the migrated artifacts to WebSphere Process Server, the migrated content causes high CPU use when calling the BusObj API. The thread dump shows that many threads are waiting, which causes a degradation in performance:

    at java.util.HashMap.get(HashMap.java:346)

    at CxCommon.BusinessObject.

    (BusinessObject.java:277)

    at Collaboration.BusObj.

    (BusObj.java:94)

    Using ConcurrentHashMap instead of HashMap can bypass this Java problem.

    LOCAL FIX:

    You can possibly avoid this problem by reducing the concurrency of the system.

    USERS AFFECTED:

    WebSphere Process Server v6.2.0.1 users who migrate a WebSphere InterChange Server repository.

    PROBLEM DESCRIPTION:

    After migration of a WebSphere InterChange Server repository and deployment of the migrated artifacts to WebSphere Process Server, high CPU is seen in java.util.HashMap.get(), which is called by an early version of the BusObj API.

    RECOMMENDATION:

    None

    PROBLEM SUMMARY:

    java.util.HashMap is not thread-safe and can cause high CPU use when HashMap is used in a concurrent setting with insufficient synchronization.

    PROBLEM CONCLUSION:

    Applying this interim fix resolves the problem. The fix for this APAR is targeted for inclusion in a future v6.2.0 fix pack.

    好明显,ibm的websphere也遇到这个问题,后来也是把hashMap换成ConcurrentHashMap解决的

    展开全文
  •   前面我们已经探究过HashMap的源码,本文主要来说明HashMap JDK1.7版本并发成环的原因。 1.2 原因探究 1.2.1 成环原因 多线程 扩容 1.2.2 源码探究 hashmap成环原因的代码出现在transfer代码中,看以下代码,...

    1.1 概述

      前面我们已经探究过HashMap的源码,本文主要来说明HashMap JDK1.7版本并发成环的原因。

    1.2 原因探究

    1.2.1 成环原因

    • 多线程
    • 扩容

    1.2.2 源码探究

    hashmap成环原因的代码出现在transfer代码中,看以下代码,transfer(),在实际扩容时候把原来数组中的元素放入新的数组中。

    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()方法把原数组中的值放到新数组中
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        //设置hashmap扩容后为新的数组引用
        table = newTable;
        //设置hashmap扩容新的阈值
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
      }
    
    void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
            //table是旧table,也就是扩容前的table
        for (Entry<K,V> e : table) {
                //e为槽位的头节点,通过头节点遍历槽位
            while(null != e) {
            	//1, 获取旧表的下一个元素
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                //通过key值的hash值和新数组的大小算出在当前数组中的存放位置
                //获得e在新table中位置
                int i = indexFor(e.hash, newCapacity);
                 
                //采用头插法
                //将e的next设置为newTable[i],也就是i对应的头节点
                //然后将e设置为newTable[i],也就是i对应的头节点
               
                //是一种头插做法
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
    

    在这里插入图片描述

    1.2.3 成环原因探究

    假设有一个hashMap数组(正常是2的N次长度,这里方便举例), 节点3上存有abc元素,此时发生扩容
    在这里插入图片描述
    此时假设有两个线程
    线程B在执行到Entry<K,V> next = e.next;后挂起,此时e指向元素a,e.next指向元素b

    线程A在new table的数组7位置依次用头插法插入3个元素后
    在这里插入图片描述 此时线程B继续执行以下代码

    
    	//next = b
    	Entry<K,V> next = e.next;   
    
    ---------------------------------------------------------------
    	//将数组7的地址赋予变量e.next  
    	 e.next = newTable[i];    
    	 
    ---------------------------------------------------------------	
    
    	//将a放到数组7的位置
    	newTable[i] = e;
    		 
    ---------------------------------------------------------------
    	//  e = next = b
    	e = next;  
    	
    

    变量e = b不是null,循环继续执行,

    
    	//  next = a
    	Entry<K,V> next = e.next;  
    
    ---------------------------------------------------------------
    	//数组7地址指向e.next
    	 e.next = newTable[i];  
    	 //将b放到数组7的位置
    	 newTable[i] = e;  
    	 //e =next = a
    	 e = next;    
    

    执行后引用关系图
    在这里插入图片描述
    此时变量e = a仍旧不为空,继续循环。。

    Entry<K,V> next = e.next;  //  变量a没有next,所以next = null
    ---------------------------------------------------------------
    // 因为newTable[i]存的是b,这一步相当于将a的next指向了b,于是问题出现了
     e.next = newTable[i];  
     ---------------------------------------------------------------
    
    newTable[i] = e; //将变量a放到数组7的位置
    e = next; // e= next = null
    ---------------------------------------------------------------
    

    当在数组7遍历节点寻找对应的key时, 节点a和b就发生了死循环, 直到cpu被完全耗尽。

    另外,如果最终线程2执行了table = newTable;那元素C就发生了数据丢失问题。

    该问题在JDK1.8修复了(尾插法),并发还是用ConcurrentHashmap。

    1.3 总结

    数组是固定长度,链表太长就需要扩充数组长度进行rehash减少链表长度。如果两个线程同时触发扩容,在移动节点时会导致一个链表中的2个节点相互引用,从而生成环链表。

    展开全文
  • 我们都知道,HashMap在并发环境下使用可能出现问题,但是具体表现,以及为什么出现并发问题,可能并不是所有人都了解这篇文章记录一下HashMap在多线程环境下可能出现的问题以及如何避免...
  • HashMap并发修改异常】

    千次阅读 多人点赞 2021-11-28 22:42:52
    我是廖志伟,一名Java开发工程师、幕后...文章目录HashMap并发修改异常使用HashTable使用工具类使用写时复制(CopyOnWrite)使用ConcurrentHashMap 本文的大概内容: HashMap并发修改异常 HashMap实际使用过程中会出.
  • hashmap并发问题的症状: hashmap多线程操作同时调用put()方法后可能导致get()死循环,从而使CPU使用率达到100%,从而使服务器宕机. Java的API文档说HashMap是非线程安全的,应该用ConcurrentHashMap。但是在这里我们...
  • JAVA HASHMAP并发访问出现的问题

    千次阅读 2017-10-20 11:28:35
    ...主要问题出在addEntry方法的new Entry (hash, key, value, e),如果两个线程都同时取得了e,则他们下一个元素都是e,然后赋值给table元素的时候有一个成功有一个丢失。 put非null元素后get
  • 我们都知道,HashMap在并发环境下使用可能出现问题,但是具体表现,以及为什么出现并发问题, 可能并不是所有人都了解,这篇文章记录一下HashMap在多线程环境下可能出现的问题以及如何避免。 在分析HashMap并发...
  • 3.为什么hashmap不安全 why 3.1 插入HashMap.put 3.1.1 HashMap 在扩容的时候 3.2 HashMap 在删除数据的时候 0.背景 经常会看到说HashMap是线程不安全的,ConcurrentHashMap是线程安全的等等说法,不禁有个疑问,...
  • 问题再现:并发下对Map进行add和遍历输出,出现java.util.ConcurrentModificationException异常 public class MapTest { public static void main(String[] args) { Map<String,String> map=new HashMap<...
  • HashMap源码和并发异常问题分析

    千次阅读 2020-05-26 14:39:00
    HashMap允许键值对为null;HashTable则不允许,会报空指针异常; HashMap<String, String> map= new HashMap<>(2); map.put(null,null); map.put("1",null); HashMap初始容量是16,扩容方...
  • HashMap并发导致死循环

    千次阅读 2019-03-14 09:39:40
     HashMap是非线程安全的,在并发场景中如果不保持足够的同步,就有可能在执行HashMap.get时进入死循环,将CPU的消耗到100%。  HashMap采用链表解决Hash冲突。因为是链表结构,那么就很容易形成闭合的链路,这样在...
  • HashMap多线程并发问题分析

    千次阅读 2017-10-29 20:09:26
    HashMap多线程并发问题分析 目录 并发问题的症状 HashMap数据结构 HashMap的rehash源代码 正常的ReHash过程 并发的Rehash过程 三种解决方案 转载: HashMap多线程并发问题分析 ...
  • HashMap的底层工作原理和并发问题

    千次阅读 2016-09-19 20:26:31
    源码分析首先来看下HashMap一个典型的构造函数:transient HashMapEntry, V>[] table;public HashMap(int capacity) { if (capacity ) { throw new IllegalArgumentException("Capacity: " + capacity); } if ...
  • 为何出现死循环简要说明HashMap闭环的详细原因cocurrentHashMap的底层机制 为何出现死循环简要说明 HashMap是非线程安全的,在并发场景中如果不保持足够的同步,就有可能在执行HashMap.get时进入死循环,将CPU的...
  • HashMap多线程并发问题

    2019-10-07 04:16:48
    HashMap多线程并发问题  HashMap并非线程安全的,在多个线程put时,会造成key之间的死循环。当另一个线程调用这个key时,get()方法会一直执行,导致线程积压,最终造成CPU满。 问题原因分析 HashMap结构  ...
  • 并发下的HashMap

    2021-06-01 13:52:14
    HashMap的容量是有限的。当经过多次元素插入,使得HashMap达到一定饱和度时,Key映射位置发生冲突的几率会逐渐提高。 这时候,HashMap需要扩展它的长度,也就是进行Resize。 影响发生Resize的因素有两个: ...
  • HashMap并发修改异常解决办法

    千次阅读 2017-04-20 19:18:56
    我在循环读取hashmap里的内容时,在循环中又在另外一个线程对hashmap...翻译过来就是并发修改异常,上网找了下资料,解决方法如下: 调用HashMap的reomve方法时会出现 java.util.ConcurrentModificationExcepti
  • 但凡先查后写的,在并发环境下往往都有并发查询问题 举个例子, 比如有两个待写入的键值对 {“a”: 1, “b”: 2},刚好落到同一个桶里,假设这个桶是 0 号桶,这个桶里之前有一个元素 c。 单线程环境下 我们先查 a ...
  • 你碰到过几种HashMap在高并发下出现的问题,哪些可能出现的问题 前言: 我们都知道,HashMap在并发环境下使用可能...在分析HashMap并发问题前,先简单了解HashMap的put和get基本操作是如何实现的。 1.H...
  • 怎么解决HashMap线程不安全的问题? 1.使用HashTable替代HashMap HashTable的put操作,有synchronized关键字修饰。 2.使用Map map = Collections.synchronizedMap(new HashMap()); 这个方法实际上返回了一个...
  • HashMap 是非线程安全的,在多线程处理场景下,严禁使用。...因为在高并发情况下,HashMap在一些操作上会存在问题,如死循环问题,导致CPU使用率较高。 下面来看下怎么复现这个问题。如下代码所示,我们创建10...
  • 一,故障现象 java.util…ConcurrentModificationException并发修改异常 二,解决方案 ① new ConcurrentHashMap<>(); ②Collections.syncronizedMap(new HashMap()) 与前面体系一样 ...
  • HashMap 本身是线程不安全的,如果线程并行插入元素,可能会同时触发扩容。这里会新建一个更大的数组,并调用 transfer 方法对元素进行转移,转移的逻辑也很很好理解,就是遍历原来 table 中每个位置的节点,并对每...
  • hashmap并发情况下的成环原因

    千次阅读 2018-07-22 10:15:37
    他的线程不安全出现在,并发情况下可能会出现链表成环的问题,导致程序在执行get操作时形成死循环。 hashmap成环原因的代码出现在transfer代码中,也就是扩容之后的数据迁移部分,代码如下: void transfer(Entry...
  • 并发安全问题HashMap

    2016-09-26 11:37:16
    并发安全问题之HashMap ...并发问题的症状多线程put后可能导致get死循环多线程put的时候可能导致元素丢失put非null元素后get出来的却是nullHashMap数据结构HashMap的rehash源代码正常的ReHash过程并发的Re
  • HashMap并发出现的问题

    2017-11-06 20:50:24
    死循环并发就是不止一个线程在执行对HashMap的操作,例如添加,当HashMap中的元素超过了阀值,那么就需要扩容;在并发的环境中就可能出现两个以上的线程同时进行扩容操作,这时候就有可能线程1刚取到e及e.next的时候...
  • 我们都知道,HashMap在并发环境下使用可能出现问题,但是具体表现,以及为什么出现并发问题,在分析HashMap并发问题前,先简单了解HashMap的put和get基...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 85,734
精华内容 34,293
关键字:

如何解决hashmap并发问题