Java多线程与并发基础 (2)

不管业务中遇到怎样的多个线程访问某对象或某方法的情况,而在编程这个业务逻辑的时候,都不需要再额外做任何额外的处理(也就是可以像单线程编程一样),程序也可以正常运行(不会因为多线程而出错),就可以称为线程安全

面试题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:单例模式的作用和适用场景

单例模式:只获取一次资源,全程序通用,节省内存和计算;保证多线程计算结果正确;方便管理;
比如日期工具类只需要一个实例就可以,无需多个示例

面试题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的内存模型

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgwfd.html