CS-LogN思惟导图:记录专业基础 面试题
开源地址:https://github.com/FISHers6/CS-LogNjava

多线程与并发基础
实现多线程
面试题1:有几种实现线程的方法,分别是什么
-
1.继承Thread类,启动线程的惟一方法就是经过 Thread 类的 start()实例方法,start()方法是一个 native 方法,它将启动一个新线程去执行 run()方法git
-
2.实现 Runnable 接口,重写run()函数,做为参数放到Thread类构造函数中做为target属性,运行start()方法github
-
线程池建立线程、Callable本质仍是使Runnable建立,Callable是父辈类继承了Runnable,线程池需传入参数面试
面试题2:实现Runnable方法好,仍是继承Thread类好
-
实现Runnable接口更好算法
- 1.单一继承原则,若是继承了Thread,就不能继承其它类了,限制了可扩展性
- 2.Thread类每次只能建立一个独立的线程,损耗大,而Runnable能利用线程池工具来建立线程
- 3.从代码架构上看,run内容应该与Trhead代码解耦
面试题3:一个线程两次调用start方法会出现什么状况(考察源码)
- 第二次会出现异常,从start源码上和线程生命周期上分析,一个线程start后,
改变了threadState状态字;而第二次再start每次会先检查这个状态不是0就报异常
面试题4:既然start方法会调用run方法,为何咱们仍是要用start方法,而不是直接调用run方法呢(考察源码)
- 由于start后线程才会通过完整的线程生命周期,start调用native start0,虚拟机执startThread,thread_entry入口中调用Thread的run,
面试题5:start和run有什么区别
- run()方法:只是普通的方法,调用run普通方法,能够重复屡次调用
- start()方法,会启动一个线程,使得虚拟机去调用Runnable对象的run()方法,不能屡次启动同一个线程
面试题6:start方法如何调用run方法的(考察源码和JVM)
- start方法调用native start0,JVM虚拟机执行startThread,在thread_entry中调用Thread的run方法
面试题7:如何正确中止线程
- 使用interrupt中断通知,而不是强制,中断通知后会让被中止线程去决定什么时候中止,即把主动权交给须要被中断的线程
线程的生命周期
面试题1:Java线程有哪几种状态 说说生命周期
Thread和Object类中
与线程相关的重要方法缓存
面试题1:实现两个线程交替打印奇数偶数
面试题2:手写生产者消费者设计模式,为何用该模式
面试题3:wait后发生了什么,为何须要在同步代码内才能使用
- 从jvm的源码实现上看,wait后,线程让出占有的cpu并释放同步资源锁;把本身加入到等待池,之后不会再主动参与cpu的竞争,除非被其它notify命中
- 为了确保线程安全;另外wait会释放资源,因此确定要先拿到这个锁,能进入同步代码块已经拿到了锁
面试题4:为何线程通讯的方法wait,notify和notifyAll放在Object类,而sleep定义在Thread类里 (考察对象锁)
- 与对象的锁有关,对象锁绑定在对象的对象头中,且放在Object里,使每一个线程均可以持有多个对象的锁
面试题5:wait方法是属于Object对象的,那调用Thread.wait会怎么样
- 线程死的时候会本身notifyAll,释放掉全部的持有本身对象的锁。这个机制是实现不少同步方法的基础。若是调用Thrad.wait,干扰了咱们设计的同步业务流程
面试题6:如何选择notify仍是notifyAll
- 优先选用notifyAll,唤醒全部线程;除非业务须要每次只唤醒一个线程的
面试题7:notfiy后发生的操做,notifyAll以后全部的线程都会再次抢夺锁,若是某线程抢夺失败怎么办?
- notify后,让waiterSet等待池中的一个线程与entry_List锁池一级活跃线程一块儿竞争CPU
- 抢夺锁失败后会继续待在原锁池或原等待池,等待竞争CPU的调度
面试题8:sleep方法与notify/wait方法的异同点
- 相同点:线程都会进入waiting状态,均可以响应中断
- 不一样点:1.所属类不一样;2.wait/notify必须用在同步方法中,且会释放锁;3.sleep能够指定时间
面试题9:join方法后父线程进入什么状态
- waiting状态,join内部调用wait,子线程结束后自动调用notifyAll唤醒(jvm:exit函数)
线程安全与性能
面试题1:守护线程和普通线程的区别
- 守护线程是服务于普通线程的,而且不会影响到jvm的退出
面试题2:什么是线程安全
- 无论业务中遇到怎样的多个线程访问某对象或某方法的状况,而在编程这个业务逻辑的时候,都不须要再额外作任何额外的处理(也就是能够像单线程编程同样),程序也能够正常运行(不会由于多线程而出错),就能够称为线程安全
面试题3:有哪些线程不安全的状况,什么缘由致使的
- 1.数据争用、同时操做,如数据读写因为同时写,非原子性操做致使运行结果错误,a++
- 2.存在竞争,顺序不当,如死锁、活锁、饥饿
面试题4:什么是多线程的上下文切换,及致使的后果
- 进程线程切换要保存所须要的CPU运行环境,如寄存器、栈、全局变量等资源
- 在频繁的io以及抢锁的时候,会致使密集的上下文切换,多线程切换时,因为缓存和上下文的切换会带来性能问题
面试题5:多线程致使的开销有哪些
Java内存模型
面试题1:Java的代码如何一步步转化,最终被CPU执行的
-
- 最开始,咱们编写的Java代码,是*.java文件
- 在编译(javac命令)后,从刚才的.java文件会变出一个新的Java字节码文件.class
- JVM会执行刚才生成的字节码文件(*.class),并把字节码文件转化为机器指令
- 机器指令能够直接在CPU上执运行,也就是最终的程序执行
- JVM实现会带来不一样的“翻译”,不一样的CPU平台的机器指令又千差万别,没法保证并发安全的效果一致
面试题2:单例模式的做用和适用场景
- 单例模式:只获取一次资源,全程序通用,节省内存和计算;保证多线程计算结果正确;方便管理;
好比日期工具类只须要一个实例就能够,无需多个示例
面试题3:单例模式的写法,考察(重排序、单例和高并发的关系)
-
饿汉式(静态常量、静态代码块)
- 原理1:static静态常量在类加载的时候就初始化完成了,且由jvm保证线程安全,保证了变量惟一
- 原理2:静态代码块中实例化和静态常量相似;放在静态代码块里初始化,类加载时完成;
- 特征:简单,但没有懒加载(须要时再加载)
-
懒汉式(加synchronized锁)
- 对初始化的方法加synchronized锁达到线程安全的目的,但效率低,多线程下变成了同步
- 懒汉式取名:用到的时候才去加载
-
双重检查
-
代码实现
- 属性加volatile,两次if判断NULL值,第二次前加类锁
-
优势
-
为何用双重而不用单层
-
静态内部类
- 须要理解静态内部类的优势,懒汉式加载,jvm加载顺序
-
枚举
-
代码实现简单
- public enum Singleton{
INSTANCE;
public void method(){}
}
-
保证了线程安全
- 枚举是一个特殊的类,通过反编译查看,枚举最终被编译成一个final的类,继承了枚举父类。各个实例经过static定义,本质就是一个静态的对象,全部第一次使用的时候采起加载(懒加载)
-
避免反序列化破坏单例
- 避免了:好比用反射就绕过了构造方法,反序列化出多个实例
面试题4:单例模式各类写法分别适用的场合
- 1.最好的方法是枚举,因枚举被编译成final类,用static定义静态对象,懒加载。既保证了线程安全又避免了反序列化破坏单例
- 2.若是程序一开始要加载的资源太多,考虑到启动速度,就应该使用懒加载
- 3.若是是对象的建立须要配置文件(一开始要加载其它资源),就不适合用饿汉式
面试题5:饿汉式单例的缺点
面试题6:懒汉式单例的缺点
面试题7:单例模式的双重检查写法为何要用double-check
面试题8:为何双重检查要用volatile
-
1.保证instance的可见性
- 类初始化分红3条指令,重排序带来NPE空虚指针问题,加volatile防止重排序
-
2.防止初始化指令重排序
面试题9:讲一讲什么是Java的内存模型
- 1.是一组规范,须要JVM实现遵照这个规范,以便实现安全的多线程程序
2.volatile、synchronized、Lock等同步工具和关键字实现原理都用到了JMM
3.重排序、内存可见性、原子性
面试题10:什么是happens-before,规则有哪些
面试题11:讲一讲volatile关键字
- volatile是一种同步机制,比synchronized或者Lock相关类更轻量,由于使用volatile并不会发生上下文切换等开销很大的行为。而加锁时对象锁会阻塞开销大。
- 可见性,若是一个变量别修饰成volatile,那么JVM就知道了这个变量可能会被并发修改;
- 不能保证原子性
面试题12:volatile的适用场合及做用
-
做用
- 1.保证可见性 2.禁止指令重排序(单例双重锁时)
-
适合场景
- 适用场合1:boolean flag,布尔具备原子性,可再由volatile保证其可见性
- 适用场合2:做为刷新以前变量的触发器
- 但不适合非原子性操做如:a++等
面试题13:volatile和synchronized的异同
- 1 性能开销方面: 锁开销更大,volatile无加锁阻塞开销
2 做用方面:volatile只能保证可见性,锁既能保证可见性,又能保证原子性
面试题14:什么是内存可见性问题,为何存在
- 多线程下,一个线程修改共享数据后,其它线程可否感知到修改了数据的线程的变化
- CPU有多级缓存,致使读的数据过时,各处理机有独自的缓存未及时更新时,与主存内容不一致
面试题15:主内存和本地内存的关系是什么
- Java 做为高级语言,屏蔽了CPU cache等底层细节,用 JMM 定义了一套读写内存数据的规范,虽然咱们再也不须要关心一级缓存和二级缓存的问题,可是,JMM 抽象了主内存和本地内存的概念。
- 线程拥有本身的本地内存,并共享主内存的数据;线程读写共享数据也是经过本地内存交换的,因此才致使了可见性问题。
面试题16:什么是原子操做,Java的原子操做有哪些
-
原子操做
- 一系列的操做,要么所有执行成功,要么所有不执行,不会出现执行一半的状况,是不可分割的。
-
1)除long和double以外的基本类型(int, byte, boolean, short, char, float)的"赋值操做"
-
2)全部"引用reference的赋值操做",不论是 32 位的机器仍是 64 位的机器
-
3)java.concurrent.Atomic.* 包中全部类的原子操做
面试题17:long 和 double 的原子性你了解吗
- 在32位上的JVM上,long 和 double的操做不是原子的,可是在64位的JVM上是原子的。
- 在32位机器上一次只能读写32位;而浮点数、long型有8字节64位;要分高32位和低32位两条指令分开写入,相似汇编语言浮点数乘法分高低位寄存器;64位不用分两次读写了
面试题18:生成对象的过程是否是原子操做
- 不是,对象生成会生成分配空间、初始化、赋值,三条指令,有可能会被重排序,致使空指针
面试题19:区分JVM内存结构、Java内存模型 、Java对象模型
面试题20:什么是重排序
- 指令实际执行顺序和代码在java文件中的顺序不一致
- 重排序的好处:提升处理速度,包括编译器优化、指令重排序(局部性原理)
死锁
面试题1:写一个必然死锁的例子
面试题2:生产中什么场景下会发生死锁
- 并发中多线程各执己见:当两个(或更多)线程(或进程)相互持有对方所须要的资源,又不主动释放,致使全部人都没法继续前进,致使程序陷入无尽的阻塞,这就是死锁。
面试题3:发生死锁必须知足哪些条件
- 1.互斥
- 2.请求和保持
- 3.不可剥夺
- 4.存储循环等待链
面试题4:如何用工具定位死锁
- 1.jstack命令在程序发生死锁后,进行堆栈分析出死锁线程
- 2.ThreadMXbean 程序运行中发现死锁,一旦发现死锁可让用户去打日志
面试题5:有哪些解决死锁问题的策略
-
1.死锁语法,不让死锁发生
-
2.死锁避免
-
3.死锁检查与恢复
- 适用资源请求分配图,一段时间内检查死锁,有死锁就恢复策略,采用恢复策略;
- 恢复方法:进程终止 、资源剥夺
-
4.鸵鸟策略(忽略死锁)
面试题6:死锁避免策略和检测与恢复策略的主要思路是什么
面试题7:讲一讲经典的哲学家就餐问题,如何解决死锁
-
何时死锁
- 哲学家各拿起本身左手边的筷子,又去请求拿右手边筷子循环请求时而阻塞
-
如何解决死锁
- 1.一次两只筷子,造成原子性操做
- 2.只容许4我的拿有筷子
面试题8:实际开发中如何避免死锁
- 设置超时时间
- 多使用并发类而不是本身设计锁
- 尽可能下降锁的使用粒度:用不一样的锁而不是一个锁,锁的范围越小越好
- 避免锁的嵌套:MustDeadLock类
- 分配资源前先看能不能收回来:银行家算法
- 尽可能不要几个功能用同一把锁:专锁专用
- 给你的线程起个有意义的名字:debug和排查时事半功倍,框架和JDK都遵照这个最佳实践
面试题9:什么是活跃性问题?活锁、饥饿和死锁有什么区别
-
活锁
-
饥饿
- 当线程须要某些资源(例如CPU),可是却始终得不到,可能缘由是饥饿线程的优先级太低