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等相关的内容。这一篇的内容有点多了,我们放在后续一步一步来分享吧(话外音:且听下回分解)
来源:oschina
链接:https://my.oschina.net/u/4450329/blog/4776033