线程安全原理简析及HashMap多线程并发5种场景异常分析(3)

简介: 线程安全原理简析及HashMap多线程并发5种场景异常分析(3)

hashmap插入


(1)table==null? 初始化线程A执行check操作后,发生线程切换,B也check table==null操作,A、B都会resize()更新table,产生更新丢失!


if ((tab = table) == null || (n = tab.length) == 0)//(1)线程切换
    n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)//(2)线程切换
    tab[i] = newNode(hash, key, value, null);


(2)tab[i]==null?  A 线程和 B 线程计算出相同的哈希值对应了相同的数组位置,此时该位置还没数据,然后对同一个数组位置,两个线程会同时 写入新的头结点,那B的写入操作就会覆盖 A 的写入,造成 A 的写入操作丢失。


hashmap扩容


HashMap 插入后超过阈值会触发扩容resize操作,new一个新容量cap的数组,对原数组的键值对重新进行计算hash并写入新数组,然后指向新数组。


if (++size > threshold)// 线程切换
    resize();


当A、B线程同时进来,检测到总数量超过阈值的时候就会同时触发 resize 操作,各自生成新的数组并 rehash 后赋给该 map 底层的数组,结果最终只有最后一个线程生成的新数组被赋给该 map 底层,其他线程的均会丢失。


hashmap删除


删除这一块可能会出现两种线程安全问题


image.png


1、线程A判断得到了指定的数组位置i并进入了循环,此时,线程B已经删掉位置i数据了,然后线程A那边就没了。但是删除的话,没了倒问题不大,只是A返回的就是

null


2、当A、B线程同时操作同一个数组位置的时候,也都会先取得现在状态下该位置存储的头结点,然后各自去进行计算操作,之后再把结果写会到该数组位置去,其实写回的时候可能其他的线程已经就把这个位置给修改过了,就会覆盖其他线程的修改。


jdk7下HashMap的扩容和链表死循环发生的场景


在addEntry的方法中有以下代码。resize(2*table.length);可以看出是将数组扩容成原来数组的两倍。先从判断语句开始看。执行扩容的条件是当HashMap创建的节点数大于阈值的时候并且该索引位置不为空才会进行扩容。也就是说16的默认阈值是12的情况下,前十二个索引都被使用了,第十三次在索引为空的地方创建新的节点,那就暂时不需要扩容,先把这个索引位置的节点名额用了再说。


if ((size >= threshold) && (null != table[bucketIndex])) {
            resize(2 * table.length);
            hash = (null != key) ? hash(key) : 0;
            bucketIndex = indexFor(hash, table.length);
        }


扩容完成后就将要put的key通过hash算法和indexFor求出索引,注意这时候indexFor中的table.lengh参数应该是老数组长度的两倍,扩容过后的新数组。下面主要来看resize扩容方法。


在resize中发现它根据newCapacity创建了一个新的数组,而这个newCapacity就是2*table.length,在创建完成新的数组后,将老数组中的内容转移到新数组内。通过transfer方法。在transfer方法中遍历了table数组,当e(这里的e是老数组中的e)不为空的时候进行转移操作,这里rehash默认是false,没有什么特殊情况,方法体不会被执行


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


单线程下的扩容过程[头插法]

首先是要获得e节点的next指针,然后重新通过hash算法和indexFor方法计算得到新数组的索引,得到索引后开始转移。大致的画一下,在老数组中可以看到e,还有计算新的索引之前把老数组e的next指针所指向的值给了Entry<K,V> next


image.png


image.png


image.png


多线程扩容过程


假设有两个线程,从是否需要扩容判断那里开始,两个都同时都需要扩容,进入resize方法,在resize方法中两个线程都创建了各自新的数组,大小相同。然后再到transfer方法中准备转移,遍历老数组,对他们来说老数组是公共的,一样的。遍历后进入while循环,当执行到Entry<K,V> next = e.next;的时候开是发生不同,线程一有了它自己e和next,线程二也会有他自己独立的e和next


image.png


image.png


image.png


image.png


参考文章


https://blog.csdn.net/sarafina527/article/details/105040594/
https://blog.csdn.net/weixin_42769637/article/details/103304102
相关文章
|
3天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
5天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
15 0
|
18天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
22天前
|
Java 调度
Java并发编程:深入理解线程池的原理与实践
【4月更文挑战第6天】本文将深入探讨Java并发编程中的重要概念——线程池。我们将从线程池的基本原理入手,逐步解析其工作过程,以及如何在实际开发中合理使用线程池以提高程序性能。同时,我们还将关注线程池的一些高级特性,如自定义线程工厂、拒绝策略等,以帮助读者更好地掌握线程池的使用技巧。
|
3天前
|
安全 算法 Java
JavaSE&多线程&线程池
JavaSE&多线程&线程池
17 7
|
3天前
|
SQL Dubbo Java
案例分析|线程池相关故障梳理&总结
本文作者梳理和分享了线程池类的故障,分别从故障视角和技术视角两个角度来分析总结,故障视角可以看到现象和教训,而技术视角可以透过现象看到本质更进一步可以看看如何避免。
|
4天前
|
存储 缓存 NoSQL
为什么Redis使用单线程 性能会优于多线程?
在计算机领域,性能一直都是一个关键的话题。无论是应用开发还是系统优化,我们都需要关注如何在有限的资源下,实现最大程度的性能提升。Redis,作为一款高性能的开源内存数据库,因其出色的单线程性能而备受瞩目。那么,为什么Redis使用单线程性能会优于多线程呢?
17 1
|
9天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
11天前
|
存储 安全 Java
Java中的容器,线程安全和线程不安全
Java中的容器,线程安全和线程不安全
15 1
|
11天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0