jvm 如何正确使用同步块或锁来确保变量的可见性?

0yycz8jy  于 2023-01-02  发布在  其他
关注(0)|答案(2)|浏览(119)

样本代码:

public class TestTestTest {

   private void setFalseFlag() {
      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 (t.keepRunning) {}
      });
      Thread flagChanger = new Thread(() -> {
         System.out.println("before setting flag");
         t.setFalseFlag();
      });
      startLoop.start();
      Thread.sleep(1000);
      flagChanger.start();
   }
}

这个代码示例开始并且永远不会完成,因为keepRunning的更改对其他线程是不可见的。当然,如果我使用volatile或AtomicBolean来实现keepRunning,程序会正确地启动和停止。但是,据我所知,synchronized block或locks在进入和退出时提供了对主内存的刷新,或者类似于smth。信息取自文档。但我不明白如何实现它在这个代码样本。看起来它甚至不可能同步在一个监视器在这里。
Oracle Documentation
所有Lock实现都必须强制执行内置监视器锁所提供的相同内存同步语义:

  • 成功的锁定操作与成功的monitorEnter操作类似
  • 成功的解锁操作与成功的monitorExit操作类似

JSR-133常见问题解答
但是同步不仅仅是互斥。同步确保线程在同步块之前或期间的内存写入以可预测的方式对在同一监视器上同步的其他线程可见。退出同步块后,释放监视器,其效果是将缓存刷新到主内存。这样其他线程就可以看到这个线程的写操作了。在我们进入synchronized块之前,我们先获取监视器,其具有使本地处理器高速缓存无效的效果,从而将从主存储器重新加载变量。然后,我们将能够看到以前版本中可见的所有写入。
所以问题来了,我说这是不可能的,对吗?如果不可能,如何正确地做到这一点?

lf5gs5x2

lf5gs5x21#

  • “同步块或锁在进入和退出时提供对主内存的刷新”*

你不能直接访问keepRunning,这就是为什么volatile可以工作,因为你可以直接把它放在字段上,如果你想使用synchronized,你需要一段代码,它只在锁被持有时访问keepRunning(在计算机科学中,这段代码被称为“临界区”)。

// CHANGE
private synchronized void setFalseFlag() {
  // CHANGE
  run = false;
  System.out.println("keepRunning is false");
}

// CHANGE
private synchronized boolean keeRunning() {
   // CHANGE
   return run;
}

// CHANGE
private boolean run = true;

public static void main(String[] args) throws InterruptedException {
  TestTestTest t = new TestTestTest();
  Thread startLoop = new Thread(() -> {
     System.out.println("before loop");
     // CHANGE
     while (t.keepRunning()) {}
  });
qgelzfjb

qgelzfjb2#

您可以:

private Boolean keepRunning = true; //change to object so it can be synchronized one

...

while (true)
            {
                synchronized (t.keepRunning)
                {
                    if (!t.keepRunning)
                    {
                        break;
                    }
                }
            }

但是最好做volatile的事情,我认为你的版本没有崩溃的原因是java不能保证监视从其他线程改变的变量,除非它是volatile的,因此while循环检查被优化为true。

相关问题