hashMap非线程安全问题和concurrenthashmap线程安全的比较

    concurrenthashmap这个是线程安全的,使用hashmap的线程不安全主要体现在多线程调
用时,其中一个线程修改了映射结构(如添加了一个映射或删除),内容的变化是不同步的,
其他线程再调用时数据内容就出现混乱了。


HashMap底层是一个Entry数组,当发生hash冲突的时候,hashmap是采用链表的方式来解决
的,在对应的数组位置存放链表的头结点。对链表而言,新加入的节点会从头结点加入


此实现不是同步的。如果多个线程同时访问一个哈希映射,而其中至少一个线程从结构上修
改了该映射,则它必须 保持外部同步。(结构上的修改是指添加或删除一个或多个映射关
系的任何操作;仅改变与实例已经包含的键关联的值不是结构上的修改。)这一般通过对自
然封装该映射的对象进行同步操作来完成。如果不存在这样的对象,则应该使用 
Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防止
对映射进行意外的非同步访问,如下所示:


   Map m = Collections.synchronizedMap(new HashMap(...));
当多个线程都往一个HashMap里面put值的时候,如果HashMap的size大小在即将被占满时会
触发Resize()方法去重新计算HashMap容器的size大小,由于Resize()方法会重新new 
一个table去指向原来的HashMap容器,老的table会被丢弃掉。当恰巧(概率极低)有其他
线程往老的table里面put值的时候数据会丢失,在下一次需要用到这个数据时取到一个null
值,程序会在此时报空指针异常。


    concurrenthashmap是线程安全的(有加锁,实现同步效果),锁它引入了一个“分段锁”
的概念,具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来
决定把key放到哪个HashTable中。在ConcurrentHashMap中,就是把Map分成了N个Segment,
put和get的时候,都是现根据key.hashCode()算出放到哪个Segment中,通过把整个Map分为
N个Segment(类似HashTable),可以提供相同的线程安全,但是效率提升N倍,默认提升16
倍。


在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况
下,其性能下降很严重,此时ReentrantLock是个不错的方案。



***************************************关于HashMap非线程安全机制的说明***************************************************************************

1、关于HashMap
       public V put(K key, V value) {
      /******************************
      * 省略的代码:计算hash值,
* 确认key值是否已经存在,存在则直接替换并返回老值
      ******************************/
        addEntry(hash, key, value, i);
        return null;
    }
       上述put方法,在新插入一个key-value时,会调用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);
        if (size++ >= threshold)
            resize(2 * table.length);
    }
       addEntry方法在完成数据插入后,会判断当前数据量是否已经超过阈值,如果超过阈值,则调用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(newTable);
        table = newTable;
        threshold = (int)(newCapacity * loadFactor);
}
resize方法是线程非安全的!上述标黄部分是造成线程非安全的最主要原因。考虑如下场景:
线程A正在执行table = newTable,线程B正在执行table[bucketIndex] = new Entry<K,V>(hash, key, value, e) ,则线程B新增的key-value插入到老的table中,没有生效,数据丢失了
2、关于HashMapCacheStrategy
   private Map cacheMap = new HashMap();
    private Map cacheTimeMap = new HashMap();
    public boolean isObsolete(Object cacheKey) {
        if (!cacheMap.containsKey(cacheKey))
            return true;
        
        Long prevTime = (Long) cacheTimeMap.get(cacheKey);
        
        return System.currentTimeMillis() - prevTime.longValue() > cacheObsoleteTime;
}
本次问题是在出现多线程同时对HashMap执行put操作,使得cacheTimeMap在put上次数据加载时间时数据丢失,但cacheMap在put上次刷新的数据却成功导致的。
所以上述代码中标黄部分获取的值是null,标绿部分对null进行\dot操作时报nullPointException,这会致使:
1. 每次获取缓存值的时候都抛出nullPointException;
2. 每次尝试刷新缓存值时,都会调用isObsolete方法判断是否缓存过期,而每次调用均抛异常导致数据始终无法刷新,致使错误无法自动恢复;


3、结论:
       1. HashMap非线程安全,当存在两个线程同时执行put操作,而HashMap存储数据量已接近阈值触发resize操作时,容易导致数据丢失;
       2. HashMap非线程安全,当存在一个线程put操作,一个线程get操作时,当数据量已接近阈值触发resize操作时,get操作可能返回null(和本次问题无关,可以自行研究源码了解)
       3. HashMapCacheStrategy本身非线程安全,当有多个缓存同时加载,导致内部cacheTimeMap的存储量接近阈值触发resize操作时,容易导致数据丢失,且该数据丢失不可恢复;
       4. 由于HashMap非线程安全是因resize操作导致,故在系统初始化时,因频繁resize HashMap的内部存储空间,所以比较容易发生1.中所述的线程问题,稳定运行后出现1.问题的概率会极度降低。


4、改进措施:
       1. 凡是涉及到多线程操作的Map,尽可能定义为ConcurrentHashMap,对于方法内的局部变量,可以使用HashMap
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
HashMapConcurrentHashMap的区别在于线程安全性和并发性能。 HashMap线程安全的,如果多个线程同时对其进行读写操作,可能会导致数据不一致或者抛出异常。而ConcurrentHashMap线程安全的,可以在多线程环境下使用而不需要额外的同步措施。 ConcurrentHashMap保证线程安全的方式是通过分段锁技术。它将数据分成多个段(Segment),每个段都有自己的锁。当一个线程对某个段进行操作时,只需获取该段的锁,不会影响其他段的访问。这样,不同的线程可以同时操作不同的段,提高了并发性能。而HashMap没有这种机制,所有线程都需要竞争同一个锁,因此并发性能较低。 此外,ConcurrentHashMap还使用了CAS(Compare and Swap)操作和volatile关键字来保证数据的一致性。CAS操作是一种无锁的原子操作,它可以确保对变量的读写操作是原子的。volatile关键字可以保证变量的可见性,即当一个线程修改了变量的值后,其他线程可以立即看到这个修改。 综上所述,ConcurrentHashMap相较于HashMap,具有更好的线程安全性和并发性能,适用于高并发环境下的操作。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [阿里面试题:ConcurrentHashMap为什么是线程安全的?](https://download.csdn.net/download/weixin_38717171/14854002)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [HashMapconcurrentHashMap的区别](https://blog.csdn.net/qq_42949615/article/details/124109063)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [HashMapConcurrentHashMap的区别,ConcurrentHashMap线程安全吗,ConcurrentHashMap如何保证线程安全?](https://blog.csdn.net/YANG_Gang2017/article/details/80218091)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值