面试题答案
一键面试Java内存模型(JMM)中可见性问题产生的原因
- 硬件缓存与主内存的不一致:现代计算机为了提高性能,CPU通常会有自己的高速缓存。当一个线程读取共享变量时,可能会将其从主内存加载到CPU缓存中。如果另一个线程修改了主内存中的共享变量,而第一个线程的CPU缓存未及时更新,就会导致第一个线程看不到最新的值,从而产生可见性问题。
- 编译器优化与指令重排序:编译器和处理器为了优化程序性能,可能会对指令进行重排序。在单线程环境下,指令重排序通常不会影响程序的正确性。但在多线程环境中,如果重排序导致一个线程对共享变量的修改在另一个线程看来顺序错乱,就会引发可见性问题。
解决可见性问题以保证线程安全的方法
volatile关键字
- 原理:当一个变量被声明为volatile时,它会有以下特性:
- 禁止指令重排序:确保volatile变量的读写操作不会与其他指令重排序,保证了内存操作的顺序性。
- 内存可见性:对volatile变量的写操作会立即刷新到主内存,读操作会从主内存中读取最新值,而不是从缓存中读取旧值。
- 示例:
public class VolatileExample {
private volatile int sharedVariable;
public void updateVariable() {
sharedVariable = 42;
}
public int getVariable() {
return sharedVariable;
}
}
在上述代码中,sharedVariable
被声明为volatile
。当updateVariable
方法修改sharedVariable
时,修改会立即刷新到主内存,其他线程调用getVariable
方法时能读取到最新值。
synchronized关键字
- 原理:
synchronized
关键字通过获取锁来实现线程同步。当一个线程进入synchronized
块时,它会获取锁,并将该线程对应的工作内存中的共享变量刷新到主内存。当线程退出synchronized
块时,会从主内存重新读取共享变量到工作内存。这就保证了在synchronized
块内对共享变量的修改对其他线程可见。 - 示例:
public class SynchronizedExample {
private int sharedVariable;
public synchronized void updateVariable() {
sharedVariable = 42;
}
public synchronized int getVariable() {
return sharedVariable;
}
}
在上述代码中,updateVariable
和getVariable
方法都被synchronized
修饰。线程在调用这些方法时会获取对象锁,从而保证了共享变量sharedVariable
的可见性。
锁机制
- 原理:Java中的锁机制,如
ReentrantLock
,与synchronized
类似,通过控制线程对共享资源的访问顺序来保证可见性。当一个线程获取锁并修改共享变量后,释放锁前会将修改刷新到主内存。其他线程获取锁后会从主内存读取最新值。 - 示例:
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
确保了在updateVariable
和getVariable
方法中对sharedVariable
的操作的可见性。通过lock
和unlock
方法控制对共享变量的访问。