4

This program does not terminate!

public class Main extends Thread {
  private int i = 0;
  private int getI() {return i; }
  private void setI(int j) {i = j; }

  public static void main(String[] args) throws InterruptedException {
    Main main = new Main();
    main.start();

    Thread.sleep(1000);
    main.setI(10);
  }

  public void run() {
    System.out.println("Awaiting...");
    while (getI() == 0) ;
    System.out.println("Done!");
  } 
}

I understand this happens because the CPU core running the Awaiting loop always sees the cached copy of i and misses the update.

I also understand that if I make volatileprivate int i = 0; then the while (getI()... will behave[1] as if every time it is consulting the main memory - so it will see the updated value and my program will terminate.

My question is: If I make

synchronized private int getI() {return i; }

It surprisingly works!! The program terminates.

I understand that synchronized is used in preventing two different threads from simultaneously entering a method - but here is only one thread that ever enters getI(). So what sorcery is this?

Edit 1

This (synchronization) guarantees that changes to the state of the object are visible to all threads

So rather than directly having the private state field i, I made following changes:

In place of private int i = 0; I did private Data data = new Data();, i = j changed to data.i = j and return i changed to return data.i

Now the getI and setI methods are not doing anything to the state of the object in which they are defined (and may be synchronized). Even now using the synchronized keyword is causing the program to terminate! The fun is in knowing that the object whose state is actually changing (Data) has no synchronization or anything built into it. Then why?


[1] It will probably just behave as that, what actually, really happens is unclear to me

inquisitive
  • 3,549
  • 2
  • 21
  • 47
  • maybe your main thread flushes its local cache to main memory before it terminates.. – AdamSkywalker Jan 16 '19 at 13:46
  • synchronized *also* synchronizes the local memories with global memories so that writes that may have been made by other threads would be seen as expected. Putting synchronized on *only* the getter isn't guaranteed to let writes be seen. But it doesn't guarantee they won't be seen either. Nothing guarantees they won't be seen. If you make a write, it's always a possibility that other threads will notice. – kumesana Jan 16 '19 at 13:49
  • Let me throw a spanner into the works. The application will also work (ie terminate) if you simply add `System.out.print("");` to the `while` loop! No volatile and no synchronized getter. – Klitos Kyriacou Jan 16 '19 at 14:08
  • @KlitosKyriacou, good point, but I think doing `io` in that loop will defy the purpose of the experiment - which was to play with the minimal setup of independent threads. `print` is *very* heavy operation - what all happened in doing it will become difficult to analyze. – inquisitive Jan 16 '19 at 14:15
  • 1
    @inquisitive I think Kitos is talking about `System.out.println` is `synchronzied` internally. – xingbin Jan 16 '19 at 14:40
  • Reading https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5 , I don't see anything that implies the code should terminate when only the getter is synchronized. Therefore I think 竹杖芒鞋轻胜马's answer is correct - it appears to work but it's not guaranteed to work by the JLS. – DodgyCodeException Jan 17 '19 at 11:03

1 Answers1

2

It is just coincidence or platform dependent or specific JVM dependent, it is not guaranteed by JLS. So, do not depend on it.

xingbin
  • 27,410
  • 9
  • 53
  • 103
  • Can you please elaborate on why the argument *This guarantees that changes to the state of the object are visible to all threads.* does not apply? – inquisitive Jan 16 '19 at 13:53
  • 2
    @inquisitive Because it's based on *when a synchronized method exits, it automatically establishes a happens-before relationship with any subsequent invocation of a synchronized method for the same object*. However, in this case, there is only `one` thread is entering the `synchronized` block. If you `synchronized` both the getter and the setter, then it is guaranteed. – xingbin Jan 16 '19 at 13:55
  • Hi, I modified the experiment further to have both getter *and* setter synchronized, but this time I pulled the state variable to a different object (please see the modified code). Now the *guarantee of visibility* should not apply, right? cause the synchronization is on an object different from where state is being maintained. But it is still happening!! Could you please help me explain this :) – inquisitive Jan 16 '19 at 14:29
  • 1
    @inquisitive *Now the guarantee of visibility should not apply, right?* No! `synchronized` does not guarantee any specific object's visibility! If threadB enters the block after threadA exits, then it guarantees threadB can see every thing just like threadA! So if threadA set any value, then threadB can get it! You need to learn some thing about happens-before first, then you can understant them more easily. https://stackoverflow.com/questions/11970428/how-to-understand-happens-before-consistent – xingbin Jan 16 '19 at 14:35