MST

星途 面试题库

面试题:Java内存模型下的可见性问题与线程安全

请描述Java内存模型(JMM)中可见性问题产生的原因。假设有两个线程,线程A修改了一个共享变量的值,线程B如何确保能够立即看到这个修改?结合volatile关键字、synchronized关键字以及锁机制详细说明如何解决可见性问题以保证线程安全。
46.0万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

Java内存模型(JMM)中可见性问题产生的原因

  1. 硬件缓存与主内存的不一致:现代计算机为了提高性能,CPU通常会有自己的高速缓存。当一个线程读取共享变量时,可能会将其从主内存加载到CPU缓存中。如果另一个线程修改了主内存中的共享变量,而第一个线程的CPU缓存未及时更新,就会导致第一个线程看不到最新的值,从而产生可见性问题。
  2. 编译器优化与指令重排序:编译器和处理器为了优化程序性能,可能会对指令进行重排序。在单线程环境下,指令重排序通常不会影响程序的正确性。但在多线程环境中,如果重排序导致一个线程对共享变量的修改在另一个线程看来顺序错乱,就会引发可见性问题。

解决可见性问题以保证线程安全的方法

volatile关键字

  1. 原理:当一个变量被声明为volatile时,它会有以下特性:
    • 禁止指令重排序:确保volatile变量的读写操作不会与其他指令重排序,保证了内存操作的顺序性。
    • 内存可见性:对volatile变量的写操作会立即刷新到主内存,读操作会从主内存中读取最新值,而不是从缓存中读取旧值。
  2. 示例
public class VolatileExample {
    private volatile int sharedVariable;

    public void updateVariable() {
        sharedVariable = 42;
    }

    public int getVariable() {
        return sharedVariable;
    }
}

在上述代码中,sharedVariable被声明为volatile。当updateVariable方法修改sharedVariable时,修改会立即刷新到主内存,其他线程调用getVariable方法时能读取到最新值。

synchronized关键字

  1. 原理synchronized关键字通过获取锁来实现线程同步。当一个线程进入synchronized块时,它会获取锁,并将该线程对应的工作内存中的共享变量刷新到主内存。当线程退出synchronized块时,会从主内存重新读取共享变量到工作内存。这就保证了在synchronized块内对共享变量的修改对其他线程可见。
  2. 示例
public class SynchronizedExample {
    private int sharedVariable;

    public synchronized void updateVariable() {
        sharedVariable = 42;
    }

    public synchronized int getVariable() {
        return sharedVariable;
    }
}

在上述代码中,updateVariablegetVariable方法都被synchronized修饰。线程在调用这些方法时会获取对象锁,从而保证了共享变量sharedVariable的可见性。

锁机制

  1. 原理:Java中的锁机制,如ReentrantLock,与synchronized类似,通过控制线程对共享资源的访问顺序来保证可见性。当一个线程获取锁并修改共享变量后,释放锁前会将修改刷新到主内存。其他线程获取锁后会从主内存读取最新值。
  2. 示例
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int sharedVariable;
    private ReentrantLock lock = new ReentrantLock();

    public void updateVariable() {
        lock.lock();
        try {
            sharedVariable = 42;
        } finally {
            lock.unlock();
        }
    }

    public int getVariable() {
        lock.lock();
        try {
            return sharedVariable;
        } finally {
            lock.unlock();
        }
    }
}

在上述代码中,ReentrantLock确保了在updateVariablegetVariable方法中对sharedVariable的操作的可见性。通过lockunlock方法控制对共享变量的访问。