MST

星途 面试题库

面试题:Java内存模型与数据一致性深度剖析

Java内存模型(JMM)定义了Java程序在多线程环境下的内存访问规则,以确保数据一致性。请详细解释JMM中的happens - before原则,包括其各项规则以及如何通过这些规则来保证数据在多线程间的一致性。同时,说明在实际应用中,违反这些规则可能导致的数据一致性问题,并给出相应的解决方法。
15.3万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

happens - before原则概述

happens - before原则定义了两个操作之间的偏序关系。如果一个操作happens - before另一个操作,那么第一个操作的执行结果对第二个操作是可见的,并且第一个操作按顺序排在第二个操作之前。

happens - before各项规则

  1. 程序顺序规则:在一个线程内,按照程序代码顺序,前面的操作happens - before后续的操作。例如:
int a = 1; // 操作1
int b = a + 1; // 操作2,操作1 happens - before 操作2
  1. 监视器锁规则:对一个锁的解锁操作happens - before后续对这个锁的加锁操作。例如:
synchronized (this) {
    // 临界区,锁获取
} // 锁释放,happens - before 后续其他线程获取同一把锁
  1. volatile变量规则:对一个volatile变量的写操作happens - before后续对这个volatile变量的读操作。例如:
volatile int value;
// 线程1
value = 10; // 写操作,happens - before 线程2的读操作
// 线程2
int result = value; // 读操作
  1. 线程启动规则:Thread对象的start()方法happens - before此线程的每一个动作。例如:
Thread thread = new Thread(() -> {
    // 线程内操作,start() happens - before 这里的所有操作
});
thread.start();
  1. 线程终止规则:线程中的所有操作happens - before对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值等手段检测到线程已经终止执行。例如:
Thread thread = new Thread(() -> {
    // 线程内操作
});
thread.start();
thread.join(); // 线程终止,线程内所有操作 happens - before 这里
  1. 线程中断规则:对线程interrupt()方法的调用happens - before被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()检测到是否发生中断。例如:
Thread thread = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // 线程内操作
    }
});
thread.start();
thread.interrupt(); // 调用interrupt() happens - before 线程内检测到中断
  1. 对象终结规则:一个对象的初始化完成(构造函数执行结束)happens - before它的finalize()方法的开始。

保证数据在多线程间一致性的原理

通过上述happens - before规则,JMM确保了在满足这些规则的情况下,数据在多线程间的可见性和有序性,从而保证一致性。例如,通过volatile变量规则,当一个线程修改了volatile变量,其他线程能立即看到修改后的值;监视器锁规则保证了临界区的互斥访问,同一时间只有一个线程能进入临界区,避免数据竞争。

违反规则导致的数据一致性问题

  1. 可见性问题:如果不遵循volatile变量规则或监视器锁规则,一个线程对共享变量的修改可能对其他线程不可见。例如:
int sharedVariable;
// 线程1
sharedVariable = 10;
// 线程2
int result = sharedVariable; // 可能读取到旧值,因为没有遵循相关规则保证可见性
  1. 有序性问题:不遵循程序顺序规则或happens - before的其他规则,可能导致指令重排序,使得程序执行结果不符合预期。例如:
int a, b;
// 线程1
a = 1;
b = 2;
// 线程2
if (b == 2 && a == 0) { // 由于指令重排序,可能出现这种不符合预期的情况
    // 异常逻辑
}

相应的解决方法

  1. 使用volatile关键字:对于共享且需要保证可见性的变量,声明为volatile,遵循volatile变量规则。例如:
volatile int sharedVariable;
  1. 使用synchronized关键字:对于临界区代码,使用synchronized进行同步,遵循监视器锁规则。例如:
synchronized (this) {
    // 临界区代码
}
  1. 使用并发工具类:如java.util.concurrent包下的Lock接口及其实现类,CountDownLatchCyclicBarrier等,这些工具类内部遵循了happens - before规则,能有效保证多线程数据一致性。例如:
Lock lock = new ReentrantLock();
lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}