Konrad Garus
Konrad Garus

Reputation: 54005

Java Memory Model: Mixing unsynchronized and synchronized

Suppose there is a class like this:

public void MyClass {
    private boolean someoneTouchedMeWhenIWasWorking;

    public void process() {
        someoneTouchedMeWhenIWasWorking = false;
        doStuff();
        synchronized(this) {
            if (someoneTouchedMeWhenIWasWorking) {
                System.out.println("Hey!");
            }
        }
    }

    synchronized public void touch() {
        someoneTouchedMeWhenIWasWorking = true;
    }
}

One thread calls process, another calls touch. Note how process clears the flag without synchronization when it starts.

With Java memory model, is it ever possible that the thread running process will see the effect of its local, unsynchronized write, even though touch happened later?

That is, if the threads execute in this order:

T1: someoneTouchedMeWhenIWasWorking = false; // Unsynchronized
T2: someoneTouchedMeWhenIWasWorking = true; // Synchronized
T1: if (someoneTouchedMeWhenIWasWorking) // Synchronized

... is it ever possible that T1 would see the value from its local cache? Can it flush what it has to memory first (overriding whatever T2 has written), and reload that value from memory?

Is it necessary to synchronize the first write or make the variable volatile?

I would really appreciate it if the answers were backed by documentation or some respectable sources.

Upvotes: 11

Views: 498

Answers (4)

assylias
assylias

Reputation: 328598

The JLS reasons in terms of happens-before relationships (hb). Let's annotate the reads and writes of your proposed execution, using the JLS convention:

T1: someoneTouchedMeWhenIWasWorking = false;   // w
T2: someoneTouchedMeWhenIWasWorking = true;    // w'
T1: if (someoneTouchedMeWhenIWasWorking)       // r

We have the following happens-before relationships:

  • hb(w, r), due to the program order rule
  • hb(w', r), due to the synchronized keyword (An unlock on a monitor happens-before every subsequent lock on that monitor): if your program executes w' before r (looking at your watch), which you assumed, then hb(w', r)

Your question is whether r is allowed to read w. This is addressed in the last section of #17.4.5:

We say that a read r of a variable v is allowed to observe a write w to v if, in the happens-before partial order of the execution trace:

  • r is not ordered before w (i.e., it is not the case that hb(r, w)), and
  • there is no intervening write w' to v (i.e. no write w' to v such that hb(w, w') and hb(w', r)).

Informally, a read r is allowed to see the result of a write w if there is no happens-before ordering to prevent that read.

  • The first condition is clearly fulfilled: we determined that hb(w, r).
  • The second condition is also true because there is no happens-before relationship between w and w'

So unless I missed something, it seems that r could in theory observe either w or w'.

In practice, as pointed out by other answers, typical JVMs are implemented in a way that is more strict that the JMM and I don't think any current JVM would allow r to observer w, but that is not a guarantee.

In any case, the problem is easy enough to fix, either with volatile or by including w within the synchronized block.

Upvotes: 3

Marko Topolnik
Marko Topolnik

Reputation: 200148

With Java memory model, is it ever possible that the thread running process will see the effect of its local, unsynchronized write, even though touch happened later?

I see a subtle but important oversimplification going on here: how do you think you would determine that the write action by the second thread occurred exactly between the write and read actions by the first thread? To be able to tell that, you would need to assume that a write action is an idealized point on the time line and that all writes happen in sequential order. On actual hardware, a write is a very complex process involving CPU pipeline, store buffers, L1 cache, L2 cache, the front-side bus, and finally RAM. The same kind of process goes on concurrently on all CPU cores. So what exactly do you mean by one write "happening after" the other?

Further, consider the "Roach Motel" paradigm, which seems to help many people as a sort of a "mental shortcut" into the consequences of the Java Memory Model: your code can legally be transformed into

public void process() {
    doStuff();
    synchronized(this) {
        someoneTouchedMeWhenIWasWorking = false;
        if (someoneTouchedMeWhenIWasWorking) {
            System.out.println("Hey!");
        }
    }
}

This is a completely different approach to exploiting the liberties provided by the Java Memory Model to gain performance. The read inside the if-clause will indeed require an actual memory address read, as opposed to inlining the value of false directly and consequently deleting the whole if-block (this would actually happen without synchronized), but the write of false will not necessarily write through to RAM. In the next step of reasoning the optimizing compiler may decide to delete the assignment to false altogether. Depending on the specifics of all other parts of the code this is one of the things that could happen, but there are many other possibilities as well.

The main message to take away from the above should be: don't pretend you can reason away on Java code using some simplified "local cache invalidation" concept; instead stick to the official Java Memory Model and consider all liberties it offers actually taken.

Is it necessary to synchronize the first write or make the variable volatile?

With the above discussion in mind, I hope you realize that this question actually has no substance. You will either observe the write by the other thread and thus be guaranteed to observe all the other actions it did before that write, or you won't observe it and won't have the guarantees. If your code is free of data races, either outcome will work for you. If it has data races, then better fix that instead of trying to fix your idiom.

Finally, imagine that your variable is volatile. What will that give you that you don't have now? In terms of the JMM formalism the two writes will have a definite order between them (imposed by the total synchronization order), but you will be in exactly the same position as you are now: that definite order will be arbitrary in any specific execution.

Upvotes: 3

Jean Logeart
Jean Logeart

Reputation: 53819

To explain when the value is updated, let me quote from this 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.

Therefore, the value of someoneTouchedMeWhenIWasWorking is published to main memory when one thread exits a synchonized block. The published value is then read from main memory when entering synchronized blocks.

Outside the synchronized blocks, the value can be cached. But when entering a synchronized block, it will be read from main memory, and it will be published when exiting it, even though it is not declared volatile.


There is an interesting answer about this question here: https://stackoverflow.com/questions/21583448/what-can-force-a-non-volatile-variable-to-be-refreshed

Followed by a conversation here: https://chat.stackoverflow.com/rooms/46867/discussion-between-gray-and-voo

Note that in the last example, @SotiriosDelimanolis tries do to: synchronized(new Object()) {} in the while(!stop) block but it didn't stop. He added that with synchronized(this), it did. I think this behavior is explained in the FAQ when it states:

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.

Upvotes: 1

Arnab Biswas
Arnab Biswas

Reputation: 4605

The synchronized block within the process() makes sure that latest value from the main memory is picked up. Hence the code is safe as it is.

This is a good use case for volatile variable. "someoneTouchedMeWhenIWasWorking" should atleast be variable.

In your example code, synchronized is used to guard "someoneTouchedMeWhenIWasWorking" which is nothing but a flag. If it's made as volatile, the "synchronized" block/method can be removed.

Upvotes: 1

Related Questions