面试题答案
一键面试主内存与工作内存的关系
- 定义
- 主内存:是所有线程共享的内存区域,存储了共享变量(类的成员变量、静态变量等)。所有线程对这些共享变量的操作都需要通过主内存进行。
- 工作内存:每个线程都有自己独立的工作内存,线程对共享变量的操作是在工作内存中进行的。线程从主内存将共享变量拷贝到自己的工作内存,对其进行操作后,再将修改后的值写回主内存。
- 交互方式
- 线程对共享变量的读取和写入操作不是直接与主内存交互,而是通过工作内存间接进行。这种机制可能会导致线程之间的数据不一致问题,因为不同线程的工作内存中的数据可能是主内存中数据的不同副本。
锁机制通过JMM保证线程安全
- 锁与内存可见性
- 当线程获取锁时,会将主内存中的共享变量最新值刷新到自己的工作内存中。
- 当线程释放锁时,会将工作内存中共享变量的修改值写回到主内存中。
- 这样,通过锁机制,在获取锁和释放锁的过程中,保证了不同线程之间对共享变量的操作具有内存可见性,即一个线程对共享变量的修改能够被其他线程及时看到。
- 锁与原子性
- 锁机制通过互斥访问来保证原子性。当一个线程获取到锁时,其他线程无法同时获取该锁,也就无法同时访问被锁保护的代码块或资源。这确保了在同一时刻只有一个线程能够执行被锁保护的操作,从而保证了操作的原子性。
synchronized
关键字在内存可见性和原子性方面的实现原理
- 内存可见性
synchronized
关键字修饰的代码块或方法在进入时,会将主内存中的共享变量值刷新到工作内存;在退出时,会将工作内存中的共享变量值写回主内存。这就保证了不同线程对共享变量操作的内存可见性。- 例如,以下代码:
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
}
- 当一个线程进入`increment`方法时,会将`count`变量从主内存刷新到工作内存;当方法执行完毕退出时,会将工作内存中修改后的`count`值写回主内存,其他线程再次进入该方法时就能看到最新的值。
2. 原子性
- synchronized
关键字通过互斥锁实现原子性。当一个线程获取到synchronized
锁时,其他线程无法获取该锁,因此无法同时执行被synchronized
修饰的代码块或方法。
- 例如上述代码中的increment
方法,由于被synchronized
修饰,同一时刻只有一个线程能够执行count++
操作,保证了该操作的原子性,不会出现多个线程同时修改count
导致数据不一致的情况。
ReentrantLock
在内存可见性和原子性方面的实现原理
- 内存可见性
ReentrantLock
的lock()
方法和unlock()
方法同样保证了内存可见性。当线程调用lock()
获取锁时,会将主内存中的共享变量值刷新到工作内存;当线程调用unlock()
释放锁时,会将工作内存中的共享变量值写回主内存。- 示例代码如下:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
}
- 当线程进入`increment`方法并调用`lock.lock()`获取锁时,`count`变量从主内存刷新到工作内存;调用`lock.unlock()`释放锁时,工作内存中修改后的`count`值写回主内存,保证了不同线程间对`count`变量操作的内存可见性。
2. 原子性
- ReentrantLock
通过内部的AQS(AbstractQueuedSynchronizer)框架实现互斥锁,从而保证原子性。当一个线程调用lock()
获取锁成功后,其他线程调用lock()
会被阻塞,直到当前线程调用unlock()
释放锁。
- 上述increment
方法中,由于使用ReentrantLock
进行同步,同一时刻只有一个线程能够执行count++
操作,保证了该操作的原子性。