不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要再额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全
面试题3:有哪些线程不安全的情况,什么原因导致的1.数据争用、同时操作,如数据读写由于同时写,非原子性操作导致运行结果错误,a++
2.存在竞争,顺序不当,如死锁、活锁、饥饿
面试题4:什么是多线程的上下文切换,及导致的后果进程线程切换要保存所需要的CPU运行环境,如寄存器、栈、全局变量等资源
在频繁的io以及抢锁的时候,会导致密集的上下文切换,多线程切换时,由于缓存和上下文的切换会带来性能问题
面试题5:多线程导致的开销有哪些
1.上下文切换开销,如保存缓存(cache、快表等)的开销
2.同步协作的开销(java内存模型)
为了数据的正确性,同步手段往往会使用禁止编译器优化(如指令重排序优化、锁粗化等),性能变差
使CPU内的缓存失效(比如volatile可见性让自己线程的缓存失效后,必须使用主存来查看数据)
Java内存模型 面试题1:Java的代码如何一步步转化,最终被CPU执行的
最开始,我们编写的Java代码,是*.java文件
在编译(javac命令)后,从刚才的.java文件会变出一个新的Java字节码文件.class
JVM会执行刚才生成的字节码文件(*.class),并把字节码文件转化为机器指令
机器指令可以直接在CPU上执运行,也就是最终的程序执行
JVM实现会带来不同的“翻译”,不同的CPU平台的机器指令又千差万别,无法保证并发安全的效果一致
面试题2:单例模式的作用和适用场景单例模式:只获取一次资源,全程序通用,节省内存和计算;保证多线程计算结果正确;方便管理;
比如日期工具类只需要一个实例就可以,无需多个示例
饿汉式(静态常量、静态代码块)
原理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的内存模型