高级并发编程系列十四(并发集合基础)

▼魔方 西西 提交于 2020-12-05 16:54:35

1.考考你

天气转冷了,但是我们分享的热情不变,它冷它的,我们分享我们的。今天这一篇,在高级并发编程这个系列中,我们准备进入并发集合这个小节了。说到并发集合,结合你的经历,你能先想一想,应该包含哪些内容吗?或者说你期望知道哪些内容呢?

让我们一起来捋一捋吧:

  • 集合基础应该要有,不过集合基础相信你大概都知道

  • ConcurrentHashMap,面试经常被问到,没有怎么能行

  • BlockingQueue,阻塞队列用得也很多,没有也不行

  • CopyOnWriteArrayList,用得不算多,但是写时复制的思想挺好,可以有

#考考你:
1.你在你的项目中,有直接用到并发集合的案例场景吗
2.如果有,你会如何考虑选择使用哪一个并发集合呢

2.案例

2.1.段子手的自白

如果你是刚入行不久的java程序员,你对java集合框架一定很熟悉,应该还特意把它背下来过,因为教你的人告诉你:集合框架很重要。不光这样,一定还告诉过你:这个东西面试的时候还有意无意的被经常问到。于是你会特别重视

如果你是一个工作了几年,就写了几年CRUD操作的业务开发工程师。平时不留意不怎么关心技术,那么不用说,你很有可能除了ArrayList、HashMap以外,其它都没什么印象了,有没有。

直到你对你现有的老板有了成见,然后想要换坑位的时候,你才会在网络上搜一搜:ArrayList与Vector的区别;HashMap与Hashtable的区别......并统统背下来,然后出发,然后被打击。

因为你会发现人现在的面试官都不这么问了,你背的都是5年前,甚至更早的面试套路。那么现在都怎么问了呢?一上来就是:

  • 你能说一说ArrayList的底层设计思想吗(话外音:用了什么数据结构,有什么优缺点)

  • 你能说一说HashMap的底层设计思想吗(话外音:用了什么数据结构,如何解决hash碰撞)

  • 你能聊一聊ConcurrentHashMap的实现原理吗,在jdk8以后,与jdk8以前有什么区别吗

  • ......

然后你稍微想了想,好像没注意过,平常业务开发的时候也不需要关注这些啊,写的代码也没出过什么大问题。然后你会发现:这个面试官不行,不按套路出牌,不接地气,尽问些没用的

再然后过了挺久,都没有收到中意的offer,你突然觉得:以上这些东西,好像还真的挺有用......

2.2.java集合框架体系

我们通过两个类图,来展示java集合框架中Collection、与Map体系。关于类图我希望你要会看,这是一条一个初级工程师,通往高级工程师,以致架构师的必经之路。请看图:

2.2.1.Collection体系

2.2.2.Map体系

2.3.并发集合前世今生

2.3.1.并发集合第一代

第一代集合的代表,我们熟悉的有:ArrayList、HashMap、Vector、Hashtable。其中ArrayList与HashMap是线程不安全的,Vector与Hashtable是线程安全的。ArrayList与Vector相对应,HashMap与Hashtable相对应。

2.3.1.1.ArrayList与Vector

那么你知道ArrayList底层实现原理,以及Vector是如何实现线程安全的吗?关于ArrayList你应该是比较熟悉的,它的底层实现原理并不复杂。我们可以从这么几个角度来理解:

  • ArrayList是一个动态数组,它其实是一个数组(数组是基于线性表的数据结构),数组我们很熟悉对吧

  • 数组有什么特点呢?数组是连续的,支持按照下标快速随机访问,时间复杂度是O(1)

  • 数组因为要保持连续的特性,对更新不友好(增删),增加或者删除后,需要移动元素,时间复杂度是O(n)

  • 那么ArrayList它其实就是一个支持动态扩容的数组,它的特性与数组保持一致

  • 顺便问一句:你知道ArrayList每次扩容的大小吗?我建议你翻一下源码求证一下

知道了ArrayList的底层实现原理,我们也就知道了Vector的底层实现原理,原因是ArrayList与Vector是同类。它们唯一的区别是:ArrayList线程不安全,Vector线程安全。这样一来问题就简单了,我们只需要搞清楚Vector是如何实现线程安全即可。我把平常开发中,你熟悉的方法:add、get、size、iterator。截图放在一起,你一看就明白了:

从截图我们看到,Vector实现线程安全的方式,即是通过我们熟悉的同步关键字:synchronized来保障线程安全。这样有什么问题呢,主要问题在于synchronized是悲观锁,且加锁范围太大(直接在方法上加锁),安全是安全了,但是并发性能不高。也因此,我们称它是第一代并发集合。

2.3.1.2.HashMap与Hashtable

我们再来看HashMap与Hashtable。首先我们知道HashMap应用在key/value键值对的场景下。它的底层实现原理又是什么呢?它又是如何解决hash冲突的呢?

我们先来看hash的底层实现原理,HashMap底层其实是一个散列表,散列表的背后是数组。因此使用HashMap它的查找性能是很高的,时间复杂度O(1)。关于HashMap更详细的底层原理,我推荐你看我的另一个分享系列的文章:数据结构与算法系列二十(初识散列表)。里面有系统性的详细介绍,这里限于篇幅我就不重复介绍了。

我们重点来关注一下,Hashtable是如何实现线程安全的。我还是截个图,你与上面的Vector对比一看就知道了:

 

2.3.2.并发集合第二代

我们知道第一代并发集合的代表:Vector、Hashtable。它们是通过结合synchronized关键字实现线程安全,特点是并发性能不高。

那么并发集合第二代又是什么呢?我需要先跟你声明一下,并发集合第二代与第一代,本质并无区别,我只是想跟你分享一个工具类:Collections

在你的项目中,你可能见过这样的代码:

// 线程不安全的ArrayList
ArrayList<String> list = new ArrayList<>();
list.add("zhangsan");
list.add("lisi");

// 包装成线程安全的:syncList
List<String> syncList =  Collections.synchronizedList(list);

上面的代码,我们把一个线程不安全的List,通过Collections工具类,包装得到了一个线程安全的List,后续对syncList的操作都是线程安全的,是不是有点意外?

那么问题来了,它是如何实现的呢?我们还是通过截图看一下究竟:

通过截图,你已经知道了通过Collections工具类,可以把线程不安全的集合,包装成线程安全的集合,并且它的本质不变,还是通过我们熟悉的synchronized关键字。

那么你可能会有疑问了,既然如此,该工具类是不是有点多余?是的,如果你是一个勤奋的程序员,它显得特别多余;但是如果你不那么勤奋,它就很有用处了,这也是我们在项目中为什么要封装工具类的价值所在,对吧

2.3.3.并发集合第三代

并发集合第三代的内容,涉及到ConcurrentHashMap、BlockingQueue、CopyOnWriteArrayList等相关的内容。这一篇的内容有点多了,我们放在后续一步一步来分享吧(话外音:且听下回分解

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!