欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送!
在我后台回复 「资料」 可领取
编程高频电子书
!在我后台回复「面试」可领取
硬核面试笔记
!
前言
最近在更新面试突击专栏,我把每一篇将字数都尽量控制在 2000 字以内,可能在文章里边写的没有那么细致,主要是提供一些 问题
以及 回答的思路
,以及 面试中可能忽略的漏洞
,所以在看完文章之后,如果自己简历中有这方面的内容的话,一定要认真去整理一份自己的回答
,并且多查阅相关资料,如果看的文章少,就会导致学习到的内容太片面
并发编程
面试官为什么都喜欢问并发编程的问题?
如果面试的大一点的公司,用户量上来之后,那么并发包下的东西还是很容易会用到的,并且写代码时,如果对并发安全不算了解,那可能写完的代码存在许多并发上的问题,可能测试的时候没问题,到生产环境中造成严重后果!
我之前面试过唯品会,唯品会的面试官给我的印象就是很在乎你的基础,无论是并发、JVM、MySQL、Redis 原理,还是项目中使用到的技术,都会问你底层原理,我面试之后也问面试官了,为什么偏向于去问这么多技术的底层原理,面试官给的回答是因为只有了解底层的原理,你在使用的过程中才会更加注意他存在哪方面的问题,可以更好的去避免!
对于并发编程这块的内容,synchronized、CAS、AQS 的原理之前也写过一篇文章,详细内容可以点击:Java并发编程-synchronized解析
说说synchronized关键字的底层原理是什么?
下面来用 大白话
说一下原理:
synchronized 保证线程同步主要是依赖于两个 jvm 的指令:monitorenter、monitorexit 来实现的,比如说 synchronized 修饰一个代码块,那么进入代码块之前,执行 monitorenter 表示上锁,退出代码块之后,执行 monitorexit 表示解锁,以此来保证不同线程顺序执行这个代码块
并且 synchronized 在 jdk1.6 进行了优化,将锁分为了四种状态:无锁、偏向锁、轻量级锁、重量级锁
,这 4 个状态会随着竞争激烈而逐渐升级,不过偏向锁在 jdk15 之后逐渐废弃,因为维护的开销比较大
能聊聊你对CAS的理解以及其底层实现原理可以吗?
CAS 操作需要 3 个参数:要写入的内存地址、预期值、要写入的值
CAS 的原理就是,去要写入的内存地址判断,如果这个值等于预期值,那么就在这个位置上写上要写入的值
CAS 存在一些缺陷:
- 循环时间过长:如果 CAS 自旋一直不成功,会给 CPU 带来很大开销
- 只能针对一个共享变量
- 存在 ABA 问题:CAS 只检查了值有没有发生改变,如果原本值为 A,被改为 B 之后,又被改为了 A,那么 CAS 是不会发现值被改编过了的
ABA 问题解决方案:为每个变量绑定版本号,A–>B–>A 加上版本号为:A1–>B2–>A3
了解 AQS 吗?底层原理是什么?
AQS 是抽象队列同步器,其实就是一个队列,存储的是线程,AQS 的作用就是 去管理线程加锁和解锁时的阻塞、唤醒
AQS 的原理:线程在获取锁失败之后,会被封装成 Node 节点假如到 AQS 阻塞等待,当获取锁的线程释放锁之后,会从 AQS 队列中唤醒一个线程,AQS 队列如下:
这里推荐一篇讲解 AQS 源码非常好的文章:AQS源码详细解析参考文章
线程池的底层工作原理
接下来就不对线程池的细节进行讲解了,如果想要查看可以点击:线程池底层原理细节
线程池其实就是对线程做一个 池化
操作,用于线程不断创建、销毁的开销,可以重复利用线程,节省资源
线程池中的重要参数如下:
corePoolSize
:核心线程数量maximumPoolSize
:线程池最大线程数量 = 非核心线程数+核心线程数keepAliveTime
:非核心线程存活时间unit
:空闲线程存活时间单位(keepAliveTime单位)workQueue
:工作队列(任务队列),存放等待执行的任务threadFactory
:线程工厂,创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等。handler
: 拒绝策略 ,如果阻塞队列满了之后,对于新加入的任务该如何处理
除了线程池的核心参数要掌握,任务提交到线程池中的执行流程也要了解:
线程池的参数设置攻略
下边以几种设置的例子,来说明一下会出现的情况:
- 如果将 maximumPoolSize 设置为 Integer.MAX_VALUE
这时,如果瞬间任务很多,核心线程都被占用,那么会无限创建线程去处理任务,导致消耗系统不断消耗资源去创建大量线程,如果任务提交速度大于线程处理速度,系统资源很快就会被耗尽,即使内存没有崩溃,也会导致 CPU 负载很高,所以要避免将 maximumPoolSize 设置的无限大
- 如果在线程中使用无界阻塞队列
如果发生了调用超时,导致队列越来越大,那么会导致任务一直向阻塞队列中存放,内存飙升,甚至出现 OOM 问题
- 自定义拒绝策略
其实可以自己去定义拒绝策略,如果线程池无法处理更多的任务了,可以在自定义的拒绝策略中,将拒绝的任务异步化持久化到磁盘中去,之后再通过一个后台线程去定时扫描这些被拒绝的任务,慢慢执行
如果线上机器突然宕机,线程池的阻塞队列中的请求怎么办?
如果宕机,重启之后,线程池阻塞队列中的任务就会全部丢失
如果想要解决这种情况的话,有这么一个 解决方案
:在将任务提交到线程池中去的时候,先把任务在数据库中存储一份,并记录任务执行的状态:未提交
、已提交
、已完成
,执行完之后的话,将任务状态标记为 已完成
,如果宕机后,导致任务丢失,就可以去数据库中扫描任务,重新提交给线程池执行