如何使用synchronized块或锁保证变量可见性?附代码示例
如何用synchronized块或锁保证变量可见性?
你给出的示例代码无法终止,核心原因是线程对keepRunning的修改没有同步到主内存,循环线程一直读取本地缓存的旧值。虽然volatile或AtomicBoolean能解决问题,但用synchronized或Lock同样可以实现,关键是要让读写操作都基于同一个监视器/锁,触发内存同步语义。
一、用synchronized块实现的正确代码
public class TestTestTest { private void setFalseFlag() { // 修改变量时加锁,确保写入刷新到主内存 synchronized (this) { this.keepRunning = false; } System.out.println("keepRunning is false"); } private boolean keepRunning = true; public static void main(String[] args) throws InterruptedException { TestTestTest t = new TestTestTest(); Thread startLoop = new Thread(() -> { System.out.println("before loop"); while (true) { // 每次循环都加锁读取,确保从主内存获取最新值 synchronized (t) { if (!t.keepRunning) { break; } } } }); Thread flagChanger = new Thread(() -> { System.out.println("before setting flag"); t.setFalseFlag(); }); startLoop.start(); Thread.sleep(1000); flagChanger.start(); } }
原理说明:
- 进入
synchronized块时,线程会失效本地处理器缓存,强制从主内存重新加载变量值; - 退出
synchronized块时,会把线程的本地修改刷新到主内存; - 两个线程都用
t实例(也就是this)作为监视器,所以修改后的keepRunning能被循环线程实时感知到。
二、用Lock接口实现的正确代码
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class TestTestTest { private final Lock lock = new ReentrantLock(); private void setFalseFlag() { lock.lock(); try { this.keepRunning = false; } finally { lock.unlock(); } System.out.println("keepRunning is false"); } private boolean keepRunning = true; public static void main(String[] args) throws InterruptedException { TestTestTest t = new TestTestTest(); Thread startLoop = new Thread(() -> { System.out.println("before loop"); while (true) { t.lock.lock(); try { if (!t.keepRunning) { break; } } finally { t.lock.unlock(); } } }); Thread flagChanger = new Thread(() -> { System.out.println("before setting flag"); t.setFalseFlag(); }); startLoop.start(); Thread.sleep(1000); flagChanger.start(); } }
原理说明:
所有Lock实现都遵循和内置监视器锁相同的内存语义:成功的lock()操作对应monitorEnter,unlock()对应monitorExit,同样能触发缓存失效和主内存刷新,保证变量可见性。
关键注意点
- 不能只在写入时加锁:如果循环线程不通过锁读取变量,它还是会用本地缓存的旧值,无法感知到修改;
- 循环内要每次都获取锁:不能把锁放在
while条件外,否则只会读取一次主内存值,后续循环还是用本地缓存。
内容的提问来源于stack exchange,提问作者Layosh M
相关产品推荐
相关产品推荐

