MST
星途 面试题库

面试题:Java内存模型与线程安全问题

请详细描述Java内存模型(JMM),包括其各个组件以及工作原理。并结合实际场景,说明在多线程环境下,基于JMM会产生哪些线程安全问题,如何解决这些问题,例如使用锁机制、原子类等,给出具体代码示例。
26.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java内存模型(JMM)概述

Java内存模型(Java Memory Model,JMM)是一种抽象的概念,它定义了Java程序在多线程环境下如何访问共享变量的规则。JMM的目标是在不同的硬件和操作系统平台上,为Java程序提供一致的内存访问语义,确保多线程程序的正确性。

JMM的组件

  1. 主内存(Main Memory) 所有线程共享的内存区域,存储了所有共享变量。线程对共享变量的操作都需要通过主内存进行。
  2. 工作内存(Working Memory) 每个线程私有的内存区域,线程会将主内存中的共享变量拷贝到自己的工作内存中进行操作,操作完成后再将结果写回主内存。

JMM的工作原理

  1. 线程与主内存交互

    • read(读取):从主内存读取变量值到工作内存。
    • load(载入):将read操作读取到的值放入工作内存的变量副本中。
    • use(使用):将工作内存中的变量值传递给执行引擎。
    • assign(赋值):将执行引擎处理后的结果赋值给工作内存中的变量。
    • store(存储):将工作内存中的变量值传送到主内存。
    • write(写入):将store操作传来的值写入主内存中的共享变量。
  2. happens - before原则 定义了一些规则,保证在某些操作之前发生的事情,对后续操作是可见的。例如:

    • 程序顺序规则:一个线程内,按照代码顺序,前面的操作happens - before于后续的操作。
    • 监视器锁规则:对一个锁的解锁,happens - before于随后对这个锁的加锁。
    • volatile变量规则:对一个volatile域的写,happens - before于任意后续对这个volatile域的读。

多线程环境下基于JMM的线程安全问题

  1. 可见性问题
    • 问题描述:一个线程修改了共享变量的值,其他线程可能无法及时看到修改后的结果。因为线程修改的值先存于自己的工作内存,未及时写回主内存,其他线程从主内存读取的仍是旧值。
    • 解决方法:使用volatile关键字修饰共享变量,保证变量修改对其他线程的可见性;或者使用锁机制,在加锁和解锁过程中保证共享变量的正确同步。
  2. 原子性问题
    • 问题描述:对于一些复合操作(如i++,实际包含读取、加1、写入三个操作),在多线程环境下可能出现操作不完整,导致结果错误。因为多个线程可能同时读取旧值,各自加1后写回,造成数据丢失。
    • 解决方法:使用锁机制保证操作的原子性;或者使用java.util.concurrent.atomic包下的原子类,如AtomicInteger
  3. 有序性问题
    • 问题描述:为了提高性能,编译器和处理器可能会对指令进行重排序。在单线程环境下重排序不会影响最终结果,但在多线程环境下可能导致程序逻辑错误。
    • 解决方法volatile关键字禁止指令重排序;使用锁机制,保证同一时刻只有一个线程执行同步代码块,避免重排序带来的问题。

解决问题的具体代码示例

  1. 使用锁机制解决原子性和可见性问题
public class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}
  1. 使用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();
    }
}
  1. 使用volatile解决可见性问题
public class VolatileExample {
    private volatile boolean flag = false;

    public void setFlag() {
        flag = true;
    }

    public boolean isFlagSet() {
        return flag;
    }
}