基于Java HashMap的死循环的启示详解

时间:2021-08-15 20:01:16 

一、单线程改造为多线程也是个技术活

正如我们看到耗子叔叔博客里写的那样,原来是单线程的应用程序,”后来,我们的程序性能有问题,所以需要变成多线程的,于是,变成多线程后到了线上,发现程序经常占了100%的CPU“。

考虑到是淘宝的工程师曝出来的问题,他们的技术基础一般都很扎实,连他们都用错了,所以把单线程改造为多线程并不是想象中的那么简单,我认为。

你可能很不服气地反问,淘宝的工程师又怎么了,单线程改为多线程有什么难的?无非就是应用现有的多线程技术嘛,你看,我有非常强烈的线程安全意识,我知道同步、死锁、竞态条件,还知道lock free和线程安全容器,还知道各种线程安全同步构造……难道还写不出线程安全的应用程序?

实际情况是,线程安全的应用程序并不一定因为你有扎实的线程安全基础和开发经验就能够写好的。

试着举两个例子:

1、使用线程安全容器通过索引取数据

很多人知道的线程安全容器,实际使用的时候并不一定不出现BUG,下面的(有隐患的)代码就比较典型:


        static int GetFirstOrDefault(ThreadSafeList<int> list)
        {
            if (list.Count > 0)
            {
                return list[0];
            }
            return 0;
        }


上面的函数参数list如果一开始传入一个元素总数为1的列表,大家能分析出上面的代码会有什么问题吗?

关于线程安全容器,之前我恰好也总结过一篇文章<深入线程安全容器的实现方法>。线程安全容器并不真正安全,上面有问题的代码就是出自于这里。

 

2、多线程操作邮件的失误

还有就是多线程应用场景的分析可能不正确,曾经因为一个邮件收发程序的性能问题,我也大胆改造过应用程序,改来改去就出现了重大BUG,

大家可以看看我痛心疾首总结过的<基于一个应用程序多线程误用的分析详解>。

 

上面举的这两个例子,我只是想说明,多线程应用程序中,因为线程安全产生的BUG其实是很微妙的,一个考虑不周或者认识不够深刻,出现问题的可能性简直防不胜防。

二、ReHash的代价

上面第一点主要是闲谈线程安全,接着我们也说说哈希表,深刻理解消耗成本很大的ReHash。

我们平常理解中的哈希表是“以空间换时间的一种数据结构”。这样说的太久了,大家可能会有一种直观上的错觉,就是哈希表牺牲的是空间,争取的是时间。

但是,ReHash的过程其实是空间和时间的双重重大损失,因为分析源代码,我们知道ReHash的过程其实就是一个动态扩容的过程,而哈希表的扩容是个空间和时间消耗都非常惊人的内部操作。

为什么说ReHash是个空间和时间消耗都非常惊人的内部操作呢?

1、原来当我们对哈希结构的容器进行扩容时,散列表内部要重新new一个更大的数组,然后把原来数组的内容拷贝到新数组,并进行重新散列;

2、new出来的这个更大的新数组容量有多大也是一门学问,一般来说,新数组的大小会设置成原数组双倍大小的相近的一个素数(.NET中这个素数的生成还有一定的技巧)。

从1和2这两点可以看出,ReHash的代价确实非常高。在不久以前我碰巧写过一篇关于.NET容器的动态扩容的文章<解析从源码分析常见的基于Array的数据结构动态扩容机制的详解>,其中也浅显总结了.NET的HashTable的扩容机制,现在对照Java中的HashMap源码,看到熟悉的ReHash函数命名,再看一遍.NET中的实现,果然有比较才能有提高。

至于我们平时所理解的“以空间换时间“,其实是指哈希具有O(1)复杂度的数据检索效率,但它受填充因子影响,空间开销通常很大,空间利用率不高。

所以我们常常说哈希表适用于读操作频繁,写操作较少应用场景,比如把哈希表当做缓存容器,于我心有戚戚焉。

最后看到这句“有人把这个问题报给了Sun,不过Sun不认为这个是一个问题。因为HashMap本来就不支持并发。要并发就用ConcurrentHashmap…”

根据实际开发经验,线程安全的容器并不真正线程安全,会用ConcurrentHashmap也只是进入初级阶段,同时忍不住要感慨下当年如日中天风光无限的Sun。

标签:Java,HashMap,死循环
0
投稿

猜你喜欢

  • mybatis如何使用Criteria的and和or进行联合查询

    2023-02-23 00:44:13
  • 浅谈Java中方法参数传递的问题

    2021-11-06 04:24:46
  • C#微信公众号开发之接收事件推送与消息排重的方法

    2022-01-31 08:44:46
  • 亲自教你实现栈及C#中Stack源码分析

    2021-05-23 19:39:10
  • java 解决Eclipse挂掉问题的方法

    2023-02-19 14:05:55
  • Swing图形界面实现可动态刷新的验证码

    2022-03-27 08:08:50
  • 利用JavaMail发送HTML模板邮件

    2023-01-21 02:25:32
  • c#委托详解和和示例分享

    2022-10-26 12:29:41
  • android图片转换器示例

    2022-02-16 14:27:01
  • 如何用Java注解和反射实现依赖注入

    2022-11-04 11:34:10
  • Android jni调试打印char阵列的实例详解

    2022-06-18 14:55:40
  • IDEA 插件 mapper和xml互相跳转操作

    2021-07-19 02:48:13
  • 应用Java泛型和反射导出CSV文件的方法

    2022-02-25 19:50:17
  • Java 使用Socket正确读取数据姿势

    2023-09-16 12:13:43
  • java队列实现方法(顺序队列,链式队列,循环队列)

    2023-06-24 01:43:17
  • c#利用Excel直接读取数据到DataGridView

    2023-02-22 13:39:07
  • Java利用钉钉机器人实现发送群消息

    2021-06-28 23:37:33
  • 使用IDEA搭建SSM框架的详细教程(spring + springMVC +MyBatis)

    2023-04-24 07:35:45
  • Java 精炼解读数据结构的链表的概念与实现

    2022-03-02 05:17:11
  • C#之Android手机App开发

    2023-10-19 05:18:04
  • asp之家 软件编程 m.aspxhome.com