面试经验|春招在即,时间宝贵,这一定是最近的 Java 并发学习路线
多线程这部分内容确实比较高深而且每个知识点之间比较零散,让人摸不着头脑,不知道该从哪里下手。而且对于大部分学生群体来讲,很少有机会接触到高并发这方面的真实场景,平常自己敲代码也基本不会用到,所以也导致我们大部分同学都是面向面经学习,你问 synchronized,叭叭叭我能说一堆,你问 volatile,叭叭叭我也能说一堆,但总感觉差点意思,就是这些知识点是零散的,没有那么一根线把它们很好的串联起来。
所以今天我斗胆造一根线,站在小白的角度,讲讲多线程这部分我们到底要学啥,按照什么样的顺序去学,帮助各位建立一个比较完善的知识体系,形成正确的多线程世界观。后续的文章我也基本上会按照这根线写下来。
山外青山楼外楼,晚辈自知学识尚浅,大佬们若觉得有问题恳请评论区或者私聊我指正,晚辈感激不尽(抱拳)。
炼气
首先,学习多线程,你肯定得知道线程是啥吧,包括线程的一些基础概念(比如上下文切换),那么说到线程,肯定离不开进程。OK,进程和线程这两个概念其实我们在操作系统这门课中都接触过,当然并行和并发、同步与异步等这种基本概念咱也默认你学过,那么你还需要去了解一下 Java 线程和操作系统的线程有啥区别。
另外,容易被大家忽视的一点是,一项技术的出现必定不是凭空捏造的,他一定是为了某个目的而来,在某个成熟的时机应运而生。因此,你需要知道我们为啥要使用多线程,多线程的出现解决了什么问题。
掌握上面这一步,我们称之为炼气,所谓炼精化气,起步阶段需一心一意、沉心静气。
筑基
现在我们已经知道线程是啥了,那在 Java 中如何创建线程呢?为此你会接触到三种创建线程(Thread)的方式:
直接使用 Thread
Thread + Runnable
Thread + Callable + FutureTask
学会了如何创建线程,我们去翻一翻 Thread 类的源码,你会发现其中定义了 Java 线程的六种状态,也就是所谓的生命周期,它和操作系统中线程的五态模型又有啥区别和联系呢?
既然都翻了 Thread 源码,岂有不深究的道理?我们接下来去学习一下 Thread 类给我们提供了哪些控制线程的方法,它们分别能干啥,怎样影响了线程的状态:
start / run
sleep / yield
join / join(long n)
interrupt
setDaemon 守护线程
这一阶段的学习,也就是入门阶段后的第一步,我们称之为筑基。基础不牢,地动山摇。
金丹
诚然,一个程序顺序的运行多个线程本身是没有问题的,但是如果多个线程同时访问了某个共享资源,就可能会发生不可预知的现象,也就是我们常说的线程安全问题,要了解这些问题产生的根本原因,我们就需要去深刻的了解 Java 内存模型(Java Memory Model,JMM)。
为此,我们会学习到和线程安全息息相关的三大性质:
1)原子性:一个操作是不可中断的,要么全部执行成功要么全部执行失败(也可以说是提供互斥访问,同一时刻只能有一个线程对数据进行操作)
2)可见性:当一个线程修改了共享变量后,其他线程能够立即得知这个修改
3)有序性:编译器和处理器为了优化程序性能,会对指令序列进行重新排序。由于重排序的存在,可能导致多线程环境下程序运行结果出错的问题。
那么编译器和处理器在重排序时会遵守什么原则呢?为此你会了解到数据依赖性和 as-if-serial,这里简单介绍一下这两个概念:
编译器和处理器在重排序时,会遵守数据依赖性,它们不会改变存在数据依赖性关系的两个操作的执行顺序
as-if-serial 语义的意思是:不管怎么重排序,程序的执行结果不能被改变。编译器,runtime 和处理器都必须遵守 as-if-serial 语义
事实上,可见性和有序性其实是互相矛盾的两点。一方面,对于程序员来说,我们希望内存模型易于理解、易于编程,为此 JMM 的设计者要为程序员提供足够强的内存可见性保证,专业术语称之为 “强内存模型”。而另一方面,编译器和处理器则希望内存模型对它们的束缚越少越好,这样它们就可以做尽可能多的优化(比如重排序)来提高性能,因此 JMM 的设计者对编译器和处理器的限制要尽可能地放松,专业术语称之为 “弱内存模型”。
当然,对于这个问题,JMM 的设计者找到了一个很好的平衡点,那就是 happens-before,这是 JMM 最核心的概念!理解 happens-before 是理解 JMM 的关键。
知其然而知其所以然,这一阶段,我们称为金丹。
渡劫
具体到 Java 语言层面,是怎么保证线程安全的呢?也就是如何保证原子性、可见性和有序性呢?(保证有序性上文已经说过了,就是使用 happens-before 原则)。
1)对于可见性,可以使用 volatile 关键字来保证,不仅如此,volatile 还能起到禁止指令重排的作用;另外, synchronized 和 final 这俩关键字也能保证可见性。
2)对于原子性,我们可以使用锁 和 java.util.concurrent.atomic 包中的原子类来保证。(给萌新解释一下,java.util.concurrent,简称 J.U.C,就是一个包,也称为并发包。现在网上大部分博客都会直接说 JUC,对萌新不是很友好),我们可以看看 juc.atomic 中有哪些类:
当然, atomic 包下这些原子操作类保证原子性最关键的原因还是因为它们使用了 CAS 操作,于是,你需要先去深入学习一下 CAS,了解 CAS 存在的三个问题,然后再去挖一挖这些原子类的底层原理。
另外,上面我们提到的锁这个话题其实又是一个非常核心的知识点,在深入学习之前,你需要了解一下各种锁的概念:
悲观锁和乐观锁
重量级锁和轻量级锁
自旋锁
偏向锁
重入锁和不可重入锁
公平锁和非公平锁
共享锁和排他锁
另外,与锁相关的概念的还有临界区、竞态条件等,这些你都是要去了解的。
那么锁在 Java 中具体是怎么实现的呢?早先 Java 程序是靠 synchronized 关键字实现锁功能的,在我们掌握了 synchronized 的使用方式以及底层原理后,你还会接触到与 synchronized 配套的 wait/notify/notifyAll 方法。
在 Java SE 5 之后,并发包 JUC 中新增了 Lock 接口以及相关实现类(放在 java.util.concurrent.locks 包下)也可以用来实现锁功能。
为什么会新增这样一个 Lock 接口及其相关实现类呢?因为使用 synchronized 关键字会隐式地获取锁,但是它将锁的获取和释放固化了,也就是先获取再释放。当然,这种方式简化了同步的管理,可是扩展性没有显示的锁获取和释放来的好。
例如,针对一个场景,手把手进行锁获取和释放,先获得锁 A,然后再获取锁 B,当锁 B 获得后,释放锁 A 同时获取锁 C,当锁 C 获得后,再释放 B 同时获取锁 D,以此类推。这种场景下,如果使用 synchronized 关键字就不那么容易实现了,而使用 Lock 却容易许多。
它提供了与 synchronized 关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。虽然它缺少了隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁等多种 synchronized 关键字所不具备的同步特性。
另外,还有一点非常重要的是!我们可以去翻一翻实现了 Lock 接口的类,比如 ReentrantLock(大部分文章都会直接把它翻译成重入锁),你会惊讶的发现它并没有多少代码,基本所有的方法都是调用了其静态内部类 Sync 中的方法,而 Sync 类继承了 AbstractQueuedSynchronizer 类(也就是大名鼎鼎的 AQS,译为队列同步器,简称同步器)。
可以把 AQS 理解为一个用来构建锁和同步器(工具类)的框架,locks 包中的各种锁以及接下来我们会学习的 JUC 中的工具类都是基于 AQS 来实现的。
OK,关于 AQS 这篇文章就不再多说了。上面我们提到了三个并发关键字,synchronized、 volatile,和 final,可能很多小伙伴都不知道,啥?final 和并发有啥关系?当然,这些,后续文章都会写的。
本阶段的知识非常重要,并且相对来说知识点比较多也比较难,因此我们称之为渡劫。
大乘
渡劫完毕,走到这一步各位对多线程基本的知识架构已经有了一定的认知,世界观已经初步形成,最后,就是补强的过程了,我们来看看 J.U.C 这个包还有什么东西(下图没有截全):
JUC 其实可以分为五大类:
Lock 框架(locks 包)
原子类(atomic 包)
并发集合
线程池
工具类
后面三种正是我们在这一阶段需要学习的。并发集合和线程池就没啥好说的了,它们的知识点都比较集中,学习目标也很明确,网络上很容易就能找到一篇条理清晰的文章。
然后常用的工具类还是有必要学习下:
CountDownLatch
CyclicBarrier
Semaphore
Exchanger
所谓工具类嘛,那一定是封装了某些比较复杂的操作,使我们可以很简单的去完成这些操作。以 CountDownLatch 为例:在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用 Thread 类的 join 方法,让主线程等待被 join 的线程执行完之后,主线程才能继续往下执行。而 Java 并发工具类中为我们提供了这样一个类似 “倒计时” 的工具类 CountDownLatch,可以十分方便的完成这种业务场景。
另外,还有一个比较重要的类,我也不知道怎么给它分类,就是 ThreadLocal,江湖人称线程隔离术,必问高阶考点。
OK,学完了本阶段,多线程世界观已完整形成,我们称之为大乘,忘我之境,全在己心。
最后
这些知识点的导图和问题的答案详解的PDF文档都可以免费分享给大家.需要资料的小伙伴点赞关注博主,私信博主“学习”或者“资料”即可获得免费资料!
面试经验|春招在即,时间宝贵,这一定是最近的 Java 并发学习路线相关推荐
- 面试上机器-将当前时间以数码时钟的方式控制台显示(Java实现)
面试上机器-将当前时间以数码时钟的方式控制台显示(Java实现) 前言 毕业近2年了,写下人生中的第一篇:博客,不喜轻喷,不断进步,希望通过文字记录我的程序人生 : 废话 此次记录一次面试上机题,将当 ...
- 渣硕自学两年转行Android,零经验春招拿美团30w年薪offer,HR说我是头一个!
前言 本人为某末流211工科硕士, 找工作时,经常在网上找资料学习.面经刷题,为回馈朋友们,写下这篇分享贴,也算是对我求学生涯的总结. 说来令人感慨,我的考研之路异常曲折,每次都觉得自己能十拿九稳,结 ...
- 简简单单记录一下我今年第一次面试,春招美的研发岗面试经历分享!
前言 秋招已过,春招也准备开始了,很感谢站内各位大佬的面经分享,我也来写点面经反馈给大家吧.废话不多说,先给大家讲一下面试的流程: 12月20日当天晚上开始网申投简历 1月4日收到测评邮件和链接,然后 ...
- 20W字纯手打Java并发学习笔记,助力你金三银四,决战春招,必进大厂
假如阿里给了你这个机会,你却卡在三面,你会不会懊恼? 假如阿里真的让你通过,只需要你把这一块技能的底层原理摸透,你学不学? 我有一个朋友,他小厂背景.15年毕业.普通学校,这看起来确实没什么战斗力,但 ...
- Java程序员春招三面蚂蚁金服,1-3年Java开发工程师面试经验分享
前言 为什么互联网资讯这么发达,但是没有出现技术人才井喷? 为什么会出现应届生薪资倒挂多年老员工的现象? 这个世界有太多的现象都可以用**"二八定律"**来解释. 20%拿着高工资 ...
- 博彦科技笔面试-2019春招
19/3/27下午参加了博彦(西安)的Java技术岗笔试 题目比较简单,不过对我来说有些难,Java好久没碰过了. 根据后序和中序序列重建树,本来会的,但是做错了(遗憾). 数据库操作有一个统计平均值 ...
- java怎么输出liststring_春招|春招实习上岸,分享面筋回报社区(Java、Python)...
作者:小瓜子Jack 链接:https://www.nowcoder.com/discuss/172824
- 经验分享 | 我的八股文学习路线及攻略
前几天拿到字节offer了,很开心,也有很多小伙伴私信我问了很多问题,所以在这里简单分享一下我是如何学习八股文的,文章篇幅略长,希望能帮助到有需要的同学或者也是双非大三正在迷茫的你. [知识库分享] ...
- 2020德勤面试开始了吗_四大2020年春招时间曝光!
寒假来了,开心之余,小可爱们是不是也会为工作头秃? 尤其是应届毕业生,想想过年的时候一堆亲戚会跑过来"关心"就头皮发麻: "毕业后去哪儿啊,定了没?" &quo ...
最新文章
- 创建springboot出现error:connection timed out创建springboot报错显示连接超时解决方案
- Oracle约数,Oracle约束简介
- 移植U-BOOT之裁剪和修改默认参数(易用性)启动内核,以及对uboot进行分区
- springboot项目接入配置中心,实现@ConfigurationProperties的bean属性刷新方案
- C# SNMP 编程
- bootstrap table 表格支持shirt 多选_bootstrap-table 表格行内编辑实现
- 彻底解决Eclipse的控制台console按钮非常小
- eclipse中设置JVM内存
- word排版程序代码
- 一个朋友写的诗词收藏
- Excel常用电子表格公式大全1-1
- 2020年软件工程保研南大、国防科大、天大、同济、南开、北理、软件所、哈工大、哈工深、复旦经验分享
- 老外网络语言缩写总结
- 七雄争霸如何获得鸿蒙碎片,七雄争霸手游资源如何获取
- Mac鼠标光标消失怎么办?苹果电脑鼠标指针不显示的解决方法
- Why WebRTC|前世今生
- 学校计算机房电脑桌,学校机房用双机位电脑桌的制作方法
- 数据挖掘——机器学习
- Spark Bloom Filter 测试
- 《实战Java高并发程序设计》.pdf