一. 面试题及剖析
1. 今日面试题
2. 题目剖析
在前两篇文章中,壹哥给大家讲解了线程安全问题的由来,JMM线程模型,以及保证线程安全的2种手段--原子性、可见性,如果你还没看过壹哥的前2篇文章,可以移步到前文:
高薪程序员&面试题精讲系列70之如何保证线程安全?你有没有遇到过线程死锁问题
高薪程序员&面试题精讲系列71之你熟悉volatile关键字吗?内存屏障知道吗?CPU总线嗅探机制你知道吗?
接下来在本文中,壹哥会讲解保证线程安全的第3种手段--保证有序性,并且会在本文中给大家讲解线程死锁的产生原因及定位和解决办法,希望大家可以认真阅读本文哦。
二. 有序性
保证线程安全的第3个特性,就是要满足线程执行的有序性。那么为什么要保证有序性?又该如何保证有序性呢?请跟着壹哥的节奏往下走吧。
1. 有序性由来
壹哥在前面给大家讲了volatile的内存屏障和禁止指令重排序,并且讲解了指令重排序的目的,是JVM为了提高处理速度,对代码进行了编译优化。但在并发编程下,指令重排序也会带来一些安全隐患:比如有可能会导致多个线程之间不同操作的不可见。所以为了避免重排之后,操作指令的混乱,就要求每个操作指令是有序的,也就是先干嘛后干嘛,都是有要求的,不能想怎么排就怎么排。
所以从JDK 5开始,Java中提出了一个 happens-before 的规则,这个规则阐述了两个不同操作之间的内存可见性,这两个操作既可以是在同一个线程中,也可以是在不同的线程中。
2. 有序性的概念
这里的有序性是指,在JMM中,允许编译器和处理器对指令进行重排序,但重排序的过程不会影响单线程程序的执行,只会影响多线程程序并发执行的正确性。
另外,JMM先天具有有序性,它不需要通过任何手段就可以保证有序性,这就是上面提到的happens-before原则。即如果两个操作的执行次序无法根据happens-before原则推导出来,那它们就不能保证彼此之间的有序性,进而虚拟机就可以随意地对它们进行重排序。
3. happens-before原则
那这个happens-before规则的具体内容是什么样的呢?我们来看看。
根据上面的描述可知,happens-before 规则不是用来描述实际操作先后顺序的,而是用来描述不同操作之间的可见性的。总之,如果一系列操作无法保证遵循happens-before原则,就说明这段操作无法保证有序性。
当然,上面的规则很难背,我们也没必要背下来,了解知道有这么一个东西就行了。
4. 有序性的实现方式
现在我们已经知道了有序性的含义,那该如何满足有序性呢?其实我们利用之前学过的几个关键字就可以了,如下:
以上这几个关键字,在满足其原有特性的基础上,同时也都符合有序性的要求。
六. 线程死锁问题
1. 简介
多线程及多进程的出现,改善了系统资源的利用率并提高了系统的处理能力。但并发执行也带来了新的问题--死锁。这里所谓的死锁,是指多个线程因竞争同一资源而造成的一种僵局(互相等待),若无外力作用,这些线程都将无法向前推进。
如果我们的代码中出现了死锁,后果将会很严重,这是因为:
2. 原因
那么死锁产生的原因是什么?它到底是怎么产生的?我们要想解决或者避免死锁,总得先知道原因,治病得先查病因啊。
我们的项目之所以会出现死锁,通常是因为系统中拥有的不可剥夺资源,其数量不足以满足多个进程/线程运行的需要,使得进程/线程在运行过程中,会因争夺资源而陷入僵局。只有对不可剥夺资源的竞争才可能会产生死锁,对可剥夺资源的竞争是不会产生死锁的。
根据上面的定义,壹哥给大家列出了产生死锁的4个必要条件,我们需要牢牢记住这4个条件,这是面试常问点。
以上4个条件,必须同时满足才会导致死锁。我们可以参考下图来理解死锁:
3. 解决
依然我们已经知道了死锁的产生原因,要解决或者避免死锁就很简单了,只需打破上面的4个条件就可以了。
3.1 死锁定位
如果我们线上正在运行的项目产生了死锁,该如何判断与定位死锁产生的位置呢?毕竟知道哪里产生了死锁,我们才能解决死锁。现在要想定位死锁,其实有挺多的工具可以帮我们快速定位,常用的如下:
这里壹哥给大家介绍JDK自带的的jps工具,来查看产生死锁的应用id。
然后使用jstack工具来查看该应用的持锁情况。
3.2 避免死锁的开发原则
另外在开发时,我们应该避免写出会产生死锁的代码,一般遵循如下原则即可避免死锁:
三. 结语
至此,壹哥 就带各位复习了线程安全方面的面试题。本节面试题,是我们出去面试时的高频考点,我的很多学生出去面试都会遇到类似的问题,希望各位好好看看本节内容。如果你遇到类似的问题,现在知道该怎么回答了吧?如果还有不明白的地方,请在评论区给壹哥留言。