Do volatile variables require synchronized access?
Asked Answered
C

3

12

I'm having a little difficulty understanding volatile variables in Java.

I have a parameterized class that contains a volatile variable like so:

public class MyClass<T> {

    private volatile T lastValue;

    // ... other code ...

}

I have to implement certain basic operations against lastValue including a get-value-if-not-null.

Do these operations need to be synchronized? Can I get away with doing the following method?

public void doSomething() {
    String someString;
    ...
    if (lastValue != null) {
        someString += lastValue.toString();
    }
}

Or do I need to stick the null check into a synchronized block?

public void doSomething() {
    String someString;
    ...

    synchronized(this) {
        if (lastValue != null) {
            someString += lastValue.toString();
        }
    }
}

I know that for atomic operations like get and set, I should be ok without applying synchronization (eg. public T getValue() { return lastValue; } ). But I wasn't sure about non-atomic operations.

Cindicindie answered 6/6, 2013 at 15:9 Comment(3)
Rewriting someString += lastValue.toStrin(); as someString += (""+lastValue); lets you get rid of the null check altogether.Essex
@dasblinkenlight, true, but only if you're OK with the word "null" showing up in your resultant string when lastValue is null.Buchheim
@IanMcLaird Ah, you're right, I forgot that Java does that (it's .NET that doesn't do it).Essex
A
15

volatile guarantees visibility (changes made by one thread will be seen by other threads) but it does not guarantee atomicity of several operations.

So yes, lastValue could become null between if (lastValue != null) and someString += lastValue.toString(); and your code could throw a NullPointerException.

You can either add synchronization (but you will need to synchronize all write accesses to the variable), or for that simple use case, you can use a local variable:

public void doSomething() {
    String someString;
    T lastValueCopy = lastValue;
    if (lastValueCopy != null) {
        someString += lastValueCopy.toString();
    }
}
Aristotle answered 6/6, 2013 at 15:14 Comment(8)
Why would adding synchronization to the null check require adding synchronization to the atomic operations against the volatile variable?Cindicindie
do i understand correctly that there is no notion of "before" and "after" for threads except when they get synchronized at some point in time? Do thread semantics allow for a thread to see an old variable value after another thread updated it?Carissacarita
@RoddyoftheFrozenPeas You are right, you only need to synchronize all the write operations. That is required because synchronized is used here for mutual exclusion, i.e. making sure that no thread modifies your variable between those two lines. That can only work if all threads trying to write to the variable need to acquire that lock.Aristotle
@kutschkem: That could happen because they can be cached by the reading thread in it's thread-local cache, that's the reason for using volatile, if the access to the variable is not within a synchronized block.Mccomas
@Carissacarita Yes, without proper synchronization, if Thread A writes V = 1; and Thread B subsequently does print(V), you are not guaranteed that it will see what Thread A did and it might not print 1.Aristotle
@Carissacarita Exactly - the Java Memory Model defines a "happens-before" relationship, and any two access which do not have this relationship could conceptually happen in any order, even some "impossible" ones. (There's a good example in the purple box at the start of section 17.4)Anaphylaxis
Is it safe to do T lastValueCopy = lastValue? Doesn't that just assign the variable lastValueCopy to point at the same instance of T as lastValue? It seems as though the proper thing to do would be T lastValueCopy = lastValue.clone() or an equivalent deepCopy method so that lastValueCopy holds a copy of the value of lastValue rather than just a reference to it.Cindicindie
@RoddyoftheFrozenPeas It depends on what T is and what you are trying to achieve. If you just want to avoid a NPE, then my approach is fine. If the T is a mutable object and you want to do a check-then-act based on its state (say you want to do something like: if (mutableInteger.value() != 0) result = 10 / mutableInteger.value(); then my approach won't be good enough as the underlying object might be mutated from a non 0 value to 0 between the two lines, even if you use a copy of the shared reference. In that case you need a deep clone indeed.Aristotle
S
2

This depends heavily on the properties of the type T. In the simplest case T is an immutable type, that is whatever value of T you read, it will not change its internal state afterwards. In this case you need no synchronization, just read the reference into a local variable and work with it as long as you need to.

If T is a mutable type, then you need protection against the instance changing its state while you work with it. In this case you need synchronization that specifically ensures the instance of T is protected by it. Synchronizing on this doesn't generally ensure that, it doesn't stop you from altering T's state from any other place. The proper lock for a specific T must be defined by rules (and there is no language element ensuring you don't break those rules).

In the special case T is mutable and you defined your this to be the proper lock, you need synchronization - but you don't need volatile any more.

That said the use of volatile in conjuction with synchronized looks suspicious.

Scathe answered 6/6, 2013 at 15:30 Comment(1)
@loki Not necessarily, but when it becomes visible it will be in a consistent state. If the object is not immutable, when it becomes visible, the thread might see the object with some fields properly constructed and some fields having their default value (false, 0, null).Aristotle
M
0

Parallel processing does not guarantee the data update the references to their origin. In this case, you must make an explicit synchronization on the operated as a reference object. Use synchronized, wait and notify to it.

More details on: http://oracle2java.blogspot.com.br/2013/12/java-sincronizar-referencia-entre.html

Medor answered 17/12, 2013 at 6:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.