为了账号安全,请及时绑定邮箱和手机立即绑定

Java并发知识体系建立,并发工具全图鉴——网罗天下豪杰,一个都不能少

2019.07.28 12:20 4451浏览

大家好,我是悟空。先做个简单的自我介绍:
硕士毕业于世界排名Top 9的德国慕尼黑工业大学(计算机学科排名),国内毕业于同济大学,成绩专业排名Top 1,获公派留学全额奖学金。现就职于一线明星互联网公司,首次录制了实战《Java并发核心知识体系精讲》

面试久了,你会发现Java并发编程是面试的重点考察点。并且随着公司的发展,用户量级会与日俱增,并发的问题自然避免不了,因此具备处理并发能力的 Java 程序员必然是广大公司渴求的香饽饽。

接下来和大家分享我对Java并发知识的见解,全文主要分为如下7大点:

1.   Java并发工具的痛点

2.   三大类:线程安全、方便管理、互相配合

3.   第一类:为了线程安全

4.   第二类:为了管理线程

5   第三类:为了线程协作——控制并发流程

6   面试常见问题

7   继续深入学习的途径


1      Java并发工具的痛点

Java并发中,一块非常重要的内容就是对于各种工具类的理解,常见的广义的并发工具包括:线程池、ConcurrentHashMap、AtomicInteger、CopyOnWriteArrayList、ArrayBlockingQueue、synchronized、LongAdder、ThreadLocal、各种Lock、Future、CountDownLatch、Semaphore等等。

以上这些工具,大家或多或少肯定都使用过,但是如果让我们自己编写功能类似的工具类,就很容易出错,因为我们可能会考虑不周;而JDK的常用工具类是经过了千万人检验的,值得信赖且功能完善。

我们应该学会使用,并深入理解这些工具类的原理。非必须情况下,没有必要重复造轮子,因为如果让我们自己写一个经得起考验的功能完善的线程池,那其实是非常困难的,有几千行代码,实际上,能完全读懂就已经不太容易了。

从刚才那一长串的工具类的名字中可以看出,并发工具的数量很多,而且功能好像也不尽相同,不容易完全掌握。

所以我们在此,对Java中常见的工具类做一个梳理,把它们排排坐分分类,以便啃下这块“硬骨头”。

2      三大类:线程安全、方便管理、互相配合

Java并发工具按照目的,一共分为3类:

https://img2.mukewang.com/5d3e9a1e0001bae918321835.jpg

(点击放大图片)

这三类分别是:为了线程安全、为了管理线程、为了线程协作。

有了这样的分类,未来我们拿到一个不熟悉的并发工具类的时候,就可以对号入座。先建立起全局的概念,未来的学习就会容易很多。

下面我们对这三个类别分别展开描述。

3      第一类:为了线程安全

第一类是最常见的工具类,最典型的有各种锁、原子类、ConcurrentHashMap等,这些工具的目的都是帮助我们在并发的情况下保证线程安全。

就达到线程安全的工具类而言,有两个分类的角度,第一个是从底层原理来分,第二个是从使用者程序员的角度来分类。这两种分类只是视角不同,但是本质相同,是一一对应的关系。

https://img3.mukewang.com/5d3e9bc70001b60524701256.jpg

(点击放大图片)

我们以“线程安全——互斥同步——锁”为例,进行展开说明:

1   锁的分类(各种类型的锁很多,乱花渐欲迷人眼,如何拨开迷雾,看透本质?)

下图的这些分类,是从各种不同角度出发去看的,但是这些分类并不是互斥的,也就是多个类型可以并存:有可能一个锁,同时属于两种类型,比如ReentrantLock既是互斥锁,又是可重入锁。

https://img1.mukewang.com/5d3e9baa0001e92719541154.jpg

(点击放大图片)

 

2   常见的锁分别属于哪类?适用场景是什么?

可以看出,通常我们把锁分为以下这些类别:

  1. 悲观锁和乐观锁

  2. 共享锁和独占锁

  3. 公平锁和非公平锁

  4. 可重入锁和非可重入锁

  5. 可中断锁和不可中断锁

  6. 自旋锁和非自旋锁

我们以3个最常用的锁为例子,分析类型和适用场景如下:


属于哪类锁

适用场景

ReentrantLock(实现Lock接口)

  • 悲观锁

  • 独占锁

  • 可以自由设置是否是公平锁

  • 可重入锁

  • 可中断锁(有lockInterruptibly等可中断方法)

  • CAS获取锁的时候有自旋(等学了源码分析后,再更新)

ReentrantLock功能强大,适用于大部分的普通情况

synchronized关键字

  • 同时是悲观锁(重量级锁)和乐观锁(轻量级锁)

  • 独占锁

  • 非公平锁

  • 可重入锁

  • 不可中断锁

  • 自旋锁(轻量级锁的时候)

  • 在偏斜锁、轻量级锁、重量级锁之间升降级

对于Lock和synchronized的选择标准,参加本文本末的“继续学习”中的免费课部分

ReentrantReadWriteLock(实现ReadWriteLock接口)

  • 读锁是共享锁,写锁是独占锁

  • 可以自由设置是否是公平锁

  • 可重入锁,顾名思义

  • 可中断锁(有lockInterruptibly等可中断方法)

ReentrantReadWriteLock适用于读多写少的情况,合理使用可以进一步提高并发效率。

4      第二类:为了管理线程

除了刚才说到的为了线程安全的工具类之外,现在将要介绍的“管理线程”也是很重要的一类并发工具,最典型的就是线程池和Future相关类,我们先来看看线程池。

1   为了方便管理线程,提高效率——线程池.

1    线程池的重要性

线程池是非常重要的工具,如果你要成为一个好的工程师,需要比较好地掌握这个知识,很多线上问题都是因为没有用好线程池所导致的。即使你现阶段的目标还未考虑那么深远,也要知道,这基本上是面试必问的题目,而且面试官很容易从被面试者的回答中捕捉到被面试者的技术水平。

2    什么是“池”

软件中的“池”,可以理解为工厂,工厂里有一定数量的工人,工人会帮助你制造产品,但是每一个工人的招聘和培训都要花费很大成本,所以你希望尽量利用这已有的例如20个工人长期帮你做事,而不是每次都重新招聘。

如果最近生意好,我们可以适当扩招,但是工厂所能被分配到的资源是有限的,比如占地面积、资金等,所以当你真的接到过多订单的时候,你没办法无限扩招工人,所以订单只能排队,等待工人慢慢处理。

总结成两点,“池”的作用:

·       复用已有资源

·       控制资源总量

数据库连接池是这样,线程池也是如此。

 

当一个新任务过来了,我发现池里有空闲的线程,我就直接派他去干活,不需要重新创建一条线程了,要知道线程的创建和销毁可都是麻烦事;

而如果新任务过来的时候,如果池里的线程都在忙,并且现在池子的线程已经很多了,那么新任务就去排队~



3    为什么要使用线程池

总而言之是减小开销,提高效率:

1) 问题一:反复创建线程开销大

在Java中,如果每个请求到达就去创建一个新线程,开销是相当大的,因为每一个Java的线程就对应一个操作系统的线程。在实际使用中,线程的创建和销毁都是需要时间的,如果是一个非常轻量级的请求,服务器也要新创建一条线程去处理,那么有可能创建和销毁线程消耗的时间,比请求处理的时间还更长。

2) 问题二:过多的线程会占用太多内存

创建过多的线程还会导致内存溢出。活动的线程需要消耗系统资源,如果在一个JVM里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。

3) 解决以上两个问题的思路

用少量的线程——避免内存占用过多

让这部分线程都保持工作,且可以反复执行任务——避免生命周期的损耗

4    线程池好处

1.   线程池解决了线程生命周期开销问题和系统资源不足的问题

线程池刚创建的时候,会先创建一定数量的线程,比如说10个,这样当有新请求过来的时候,就可以直接从池子里取出一个已经创建好的线程,直接开始处理请求,这样就省去了创建线程的时间。

再加上对线程的重复使用,就大大减小了线程生命周期的开销,而且由于在请求到达时线程已经存在,所以消除了线程创建所带来的延迟,使用应用程序响应更快,增强了用户体验。

 

2.   合理统筹内存和CPU的使用

通过灵活适当地调整线程中的线程数目,使得既不会由于线程太多导致内存溢出(避免抛出java.lang.OutOfMemoryError: unable to create new native thread),也不会由于线程太少导致浪费CPU资源,达到了完美平衡。

 

3.   统一管理资源

使用线程池可以统一管理任务队列和线程,例如可以统一开始和结束,比单个线程逐一处理任务要更方便、更易于管理。同时这样也便于数据统计,因为每个ThreadPoolExecutor还维护一些基本统计数据,例如已完成任务的数量。

5    线程池适合应用的场合

当一个服务器接受到大量短小线程的请求时,使用线程池技术是非常合适的,它可以大大减少线程的创建和销毁次数,提高服务器的工作效率。

 

2   获取子线程的运行结果——Future相关类

Future的核心思想是:一个方法的计算过程可能非常耗时,一直在原地等待方法返回,显然不明智。可以把该计算过程放到线程池去执行,并通过Future去控制方法的计算过程,在计算出结果后直接获取该结果,这样就避免了傻傻等待造成的时间浪费。

在JDK 8中,还引入了很好用的CompletableFuture,可以完成例如“等待多个异步任务完成后,获取到结果,再利用这些结果执行接下来的任务”等更复杂的功能。

 

5      第三类:为了线程协作——控制并发流程的工具类

除了以上讲的“为了线程安全”、“为了管理线程”外,第三类并发工具类就是为了线程之间的协作互助。

控制并发流程的工具类,作用就是帮助我们程序员更容易得让线程之间合作,让线程之间相互配合,来满足业务逻辑,比如让线程A等待线程B执行完毕后再执行等合作策略。

控制并发流程的工具类简称同步工具类,主要有以下几个:

作用

说明

Semaphore

信号线,可以通过控制“许可证”的数量,来保证线程之间的配合

线程只有在拿到“许可证”后才能继续运行。相比于其他的同步器,更灵活

CyclicBarrier

线程会等待,直到足够多线程达到了事先规定的数目。一旦达到触发条件,就可以进行下一步的动作。

适用于线程之间相互等待处理结果就绪的场景

Phaser

和CyclicBarrier类似,但是计数可变

Java 7加入的

CountDownLatch

和CyclicBarrier类似,数量递减到0时,触发动作

不可重复使用

Exchanger

让两个线程在合适时交换对象

适用场景:当两个线程工作在同一个类的不同实例上时,用于交换数据

Condition

可以控制线程的“等待”和“唤醒”

是Object.wait(), notify(), notifyAll()的升级版,功能更强大,也更安全

 

6      面试常见问题


关于Java并发工具类和原理,每一个类都有非常多的知识点将会作为面试时常见的问题。



在此我们仅以ConcurrentHashMap为例,问题的难度会逐渐增大:

·       对比Hashtable, HashMap, TreeMap,它们有什么不同?

·       同样是线程安全,ConcurrentHashMap和Hashtable 的区别?

·       为什么有了Collections.synchronizedMap(),还需要ConcurrentHashMap?

·       HashMap(HashSet同理)在多线程下可能造成CPU100%,是为什么?

·       JDK8的ConcurrentHashMap也会造成CPU 100%,你知道吗?

·       HashMap在Java 7和8有什么不同?

·       ConcurrentHashMap在Java 7和8有什么不同?画一下结构图

·       为什么Map桶中超过8个才转为红黑树?

·       ConcurrentHashMap的扩容流程你了解吗?画一下流程图

 

7      继续深入学习的途径


本篇手记的并发工具类(JUC、线程池、锁类)的实战课,正在准备中,目前我的规划是,对约二十种常见的并发工具类进行深入讲解,其中就包本篇手记里含有的线程池和JUC中各种常用的类,按照上面思维导图的思路层层展开。


小伙伴们如果对并发想更深入学习,欢迎观看我的目前已经上线的相关课程

1.   免费课《Java高并发之魂:synchronized深度解析》:https://www.imooc.com/learn/1086

2.   实战课《成体系的Java并发多线程核心+内存模型+死锁——从用法到原理,面试必考》,内容介绍如下:


8      成体系的Java并发多线程核心+内存模型+死锁——从用法到原理,面试必考


1   本门实战课章节包括

1. 【实现多线程的方式到底是1种还是2种还是4种?】:本质只有一种方式,实现执行内容有 2种方式,而包装后的外在表现形式有多种形式。

2. 【启动线程的正确和错误的方式】:包含start和run的源码分析、对比等。

3. 【停止线程的正确方式(重点、难点)】:包含为什么广为流传的volatile boolean是错 误的?如何做到3方配合,完美实现线程的停止?等。

4. 【线程的6个状态、线程的生命周期】:一图胜千言(一图说清线程的整个生命周期)、究 竟什么叫“线程阻塞”?

5. 【Thread类和Object类中和并发相关的重要方法详解】:包含wait(), notify(),  notifyAll(), sleep(), join(), yield(), Thread.currentThread()等重要方法详解,从用 法到面试,面面俱到。

6. 【线程重要属性】:包含守护线程和普通线程的区别?为什么不应该利用线程优先级设计 程序?等。

7. 【线程的未捕获异常UncaughtException应该如何处理?】有哪些解决方案?

8. 【线程安全】:包含如何找出a++具体是在哪里出的错?、3类线程不安全的现象、4种需要 额外考虑线程安全的场景等。

9. 【Java内存模型详解——底层原理】(内容较多,分为上、下两章):包含对三兄弟JVM内存结构 VS Java内存模型 VS  Java对象模型的辨析、重排序、可见性、原子性详解等重要内容,学习了Java内存模型,才能 代表你真正学会了并发编程。

10. 【死锁详解】(内容较多,分为上、下两章):包含必然死锁和实际生产中发生死锁的例子、发生死锁的4个条件、如何 用jstack命令和代码这两种方式定位死锁?、修复死锁的3种方案、实际工程中如何避免死锁 ?等

 

2   期间会穿插数个【彩蛋】

1. 如何从宏观和微观两个方面来提高技术?提高技术的途径

2. 如何了解技术领域的前沿动态?

3. 工作中业务缠身,如何在业务开发中得到更多成长?

4. Java名称由来的历史趣事

5. 演示如何分析native的c/cpp代码,如何使用openJDK

6. Java异常体系

7. JRE和JDK和JVM是什么关系?Java版本升级都包括了哪些东西的升级?Java 8和Java 1.8和 JDK 8是什么关系,是同一个东西吗?JavaSE,JavaEE,JavaME是什么?

8. 本课采用“自顶向下”的编排章节方式以及好处,对比C语言的传统教材

 

3   内容丰满成体系

这门课程经过多次精剪视频,留下的都是干货,初心是希望能把更多的干货奉献给大家,把我多年以来,累计花费了数千小时的所学、所写、对并发编程的理解,总结成视频,浓缩成本门课里,方便小伙伴们学习。

 

4   课程用心准备精心制作

实战课和本门synchronized课,都是我用心去制作的:课程尽可能以实操驱动教学,因此准备了非常多的代码例子,绝不生搬硬套,绝不读概念,课程中的每一个概念,都配有通俗易懂的语言和比喻,让小伙伴们能轻松理解,还同时配有代码演示,告别停留在PPT和书本上的学习,我们用代码说话,眼见为实。

 

5   特色——每小节都配有【常见面试问题】

我们会在每章开始的时候,提出几个相对有难度的场景面试问题,然后在整章的学习中,对这些问题做出详细解答,最后再对常见面试问题进行讲解,给出回答的思路。让小伙伴们在面试中可以从用法一直讲到原理,和面试官深入交流,展示自身的知识储备和亮点,斩获心仪的Offer。

 

6   欢迎关注课程,地址:

Java并发核心知识体系精讲》

注:本门课暂不包含本篇手记的并发工具类内容(JUC、线程池、锁类),但是这方面的内容我会根据大家的需要,进行下一门课的准备。 目前我的规划是,对约二十种常见的并发工具类进行深入讲解,其中就包本篇手记里含有的线程池和JUC中各种常用的类。


《Java并发核心知识体系精讲》的三大块内容为【多线程核心基础、Java内存模型、死锁的发生和解决】,学习了以后,可以为后续并发工具类的学习打下坚实的基础。


附真实好评截图:

https://img1.mukewang.com/5d5a2fff0001ea0813161012.png

点击查看更多内容

本文原创发布于慕课网 ,转载请注明出处,谢谢合作

50人点赞

若觉得本文不错,就分享一下吧!

评论

相关文章推荐

正在加载中
意见反馈 邀请有奖 帮助中心 APP下载
官方微信

举报

0/150
提交
取消