面试题答案
一键面试Java内存模型(JMM)概述
Java内存模型(Java Memory Model,JMM)是一种抽象的概念,它定义了Java程序在多线程环境下如何访问共享变量的规则。JMM的目标是在不同的硬件和操作系统平台上,为Java程序提供一致的内存访问语义,确保多线程程序的正确性。
JMM的组件
- 主内存(Main Memory) 所有线程共享的内存区域,存储了所有共享变量。线程对共享变量的操作都需要通过主内存进行。
- 工作内存(Working Memory) 每个线程私有的内存区域,线程会将主内存中的共享变量拷贝到自己的工作内存中进行操作,操作完成后再将结果写回主内存。
JMM的工作原理
-
线程与主内存交互
- read(读取):从主内存读取变量值到工作内存。
- load(载入):将read操作读取到的值放入工作内存的变量副本中。
- use(使用):将工作内存中的变量值传递给执行引擎。
- assign(赋值):将执行引擎处理后的结果赋值给工作内存中的变量。
- store(存储):将工作内存中的变量值传送到主内存。
- write(写入):将store操作传来的值写入主内存中的共享变量。
-
happens - before原则 定义了一些规则,保证在某些操作之前发生的事情,对后续操作是可见的。例如:
- 程序顺序规则:一个线程内,按照代码顺序,前面的操作happens - before于后续的操作。
- 监视器锁规则:对一个锁的解锁,happens - before于随后对这个锁的加锁。
- volatile变量规则:对一个volatile域的写,happens - before于任意后续对这个volatile域的读。
多线程环境下基于JMM的线程安全问题
- 可见性问题
- 问题描述:一个线程修改了共享变量的值,其他线程可能无法及时看到修改后的结果。因为线程修改的值先存于自己的工作内存,未及时写回主内存,其他线程从主内存读取的仍是旧值。
- 解决方法:使用
volatile
关键字修饰共享变量,保证变量修改对其他线程的可见性;或者使用锁机制,在加锁和解锁过程中保证共享变量的正确同步。
- 原子性问题
- 问题描述:对于一些复合操作(如i++,实际包含读取、加1、写入三个操作),在多线程环境下可能出现操作不完整,导致结果错误。因为多个线程可能同时读取旧值,各自加1后写回,造成数据丢失。
- 解决方法:使用锁机制保证操作的原子性;或者使用
java.util.concurrent.atomic
包下的原子类,如AtomicInteger
。
- 有序性问题
- 问题描述:为了提高性能,编译器和处理器可能会对指令进行重排序。在单线程环境下重排序不会影响最终结果,但在多线程环境下可能导致程序逻辑错误。
- 解决方法:
volatile
关键字禁止指令重排序;使用锁机制,保证同一时刻只有一个线程执行同步代码块,避免重排序带来的问题。
解决问题的具体代码示例
- 使用锁机制解决原子性和可见性问题
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
- 使用
AtomicInteger
解决原子性问题
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
- 使用
volatile
解决可见性问题
public class VolatileExample {
private volatile boolean flag = false;
public void setFlag() {
flag = true;
}
public boolean isFlagSet() {
return flag;
}
}