文章目录
别人总结的JVM常考面试题目
cyc Java内存模型(JMM)
1、缓存一致性问题
-
处理器的速度比内存快几个数量级,为了解决这种速度矛盾,在它们之间加入了高速缓存。
-
加入高速缓存带来了一个新的问题:缓存一致性。如果多个缓存共享同一块主内存区域,那么多个缓存的数据可能会不一致,需要一些协议来解决这个问题。
-
Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存。
-
工作内存存储在高速缓存或者寄存器中,保存了该线程使用的变量的主内存副本拷贝。
-
线程只能直接操作工作内存中的变量,不同线程之间的变量值传递需要通过主内存来完成。
解决缓存一致性问题的方法
- 加锁
- MESI协议
2、内存之间的交互操作
Java内存模型规定了8个操作来完成主内存和工作内存之间的交互操作(这几个操作具有原子性,除了对于64位数据即double、long的读写):
- lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占的状态。
- unclock(解锁):作用于主内存的变量,把一个处于锁定的状态释放出来。
- read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中
- load(载入):作用于工作内存的变量,把read操作从主内存得到的变量值放入工作内存的变量副本中。
- use(使用):作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值):作用于工作内存的变量,把一个从执行引擎接收到的值 赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
- store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传递到主内存,以便write操作使用。
- write(写入):作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。
3、JMM的三大特性
- 原子性
一组操作要么全部成功,要么全部失败。
Java内存模型允许虚拟机将没有被volatile修饰的64位数据即long和double的读写分成两次32位的操作来进行,即load、store、read和write操作可以不具有原子性。
虽然上面的8个操作单独来说一般是具有原子性的,但是组合使用的时候可能会破坏原子性质,比如i++,在执行load assign store的过程之间会破坏原子性。可以使用Atomic这样的类包装原子性。 - 可见性
一个线程修改了共享变量的值,其他线程能立刻知道这个修改。
volatile可以保证可见性。 - 有序性
有序性指的是在本线程内观察,所有操作都是有序的,在一个线程观察另外一个线程,操作可能就是无序的,无序是因为发生了指令重排序。
在Java内存模型中允许编译器和处理器对指令进行重排序,重排序过程不会影响单线程程序的执行,但是会影响到多线程并发执行的正确性。
4、happen-before
JMM可以通过happens-before关系向程序员提供跨线程的内存可见性保证(如果A线程的写操作a与B线程的读操作b之间存在happens-before关系,尽管a操作和b操作在不同的线程中执行,但JMM向程序员保证a操作将对b操作可见)。
具体的定义为:
-
1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前。
-
2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)。
Happen before的八大原则
- 单线程happen-before原则:在同一个线程中,书写在前面的操作happen-before后面的操作。
- 锁的happen-before原则:对一个锁的解锁,happens-before于随后对这个锁的加锁。
- volatile的happen-before原则:对一个volatile变量的写操作happen-before对此变量的任意操作(当然也包括写操作了)。
- happen-before的传递性原则:如果A操作 happen-before B操作,B操作happen-before C操作,那么A操作happen-before C操作。
- 线程启动的happen-before原则:同一个线程的start方法happen-before此线程的其它方法。
- 线程中断的happen-before原则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生。
- 线程join的happen-before原则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
- 对象创建的happen-before原则:一个对象的初始化完成先于他的finalize方法调用。
5、如何保证线程安全
不可变
不可变对象一定是线程安全的。
栈封闭
ThreadLocal
互斥同步
使用synchronized和ReentrantLock
使用非阻塞同步CAS
CAS有三个操作数,分别是内存地址V、预期值A和新值B,当V等于A的时候,才将V的值更新为B。
AtomicInteger底层就是调用的CAS。
CAS的ABA问题
如果一个变量初次读取的时候是A,它的值被改成了B,后来又被改成了A,那么CAS操作就会误认为他从来没改变过。
JUC包提供了一个带有标记的原子引用类AtomicStampedReference来解决这个问题,他可以通过控制变量值的版本来保证CAS的正确性。