Home > Mobile >  How to properly use synchronized block or locks to ensure variable visibility?
How to properly use synchronized block or locks to ensure variable visibility?

Time:01-02

Sample code:

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();
   }
}

This code sample starts and never finishes, because keepRunning changes are not visible to the other thread. Of course, if I use volatile or AtomicBolean for keepRunning programm starts and stops properly. But, as far as I know synchronized block or locks provides flushes to main memory on entering and on exiting or smth like that, info taken from documentation. But I can't understand how to implement it on this code sample. Looks like it is not even possible to sync on one monitor here.

Oracle Documentation

All Lock implementations must enforce the same memory synchronization semantics as provided by the built-in monitor lock:

  • A successful lock operation acts like a successful monitorEnter action
  • A successful unlock operation acts like a successful monitorExit action

JSR-133 FAQ

But there is more to synchronization than mutual exclusion. Synchronization ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor. After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads. Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.

So here is the question, am I right that it is not possible here? Or if not, how to do it properly?

CodePudding user response:

"synchronized block or locks provides flushes to main memory on entering and on exiting"

You can't because you're accessing keepRunning directly. That's why volatile works, because you can put it directly on the field. If you want to use synchronized, you need a section of code that only accesses keepRunning while a lock is held (in computer science this section of code is called a "critical section").

// 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()) {}
  });

CodePudding user response:

You can do:

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

...

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

But better do volatile thingy. I think the reason your version doesn't break is that java isn't guaranteed to watch variables changed from other threads unless it's volatile, therefor the while loop check is optimized to true.

  • Related