15

... without additional synchronization ? The Tree class below is meant to be accessed by multiple threads (it is a singleton but not implemented via an enum)

class Tree {

    private volatile Node root;

    Tree() {
        root = new Node();
        // the threads are spawned _after_ the tree is constructed
    }

    private final class Node {
        short numOfKeys;
    }
}
  • Will updates to the numOfKeys field be visible to reader threads without any explicit synchronization (notice that both readers and writers have to acquire an instance of ReentrantReadWriteLock - same instance for each node - but barring that) ? If not would making numOfKeys volatile suffice ?
  • Is changing the root as simple as root = new Node() (only a writer thread would change the root, apart from the main thread which calls the Tree constructor)

Related:

EDIT: interested in post Java 5 semantics

Gray
  • 115,027
  • 24
  • 293
  • 354
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • I don't understand the part with the `ReentrantReadWriteLock`. Is there a _ReadWriteLock_? Or is the question whether it is possible without a _ReadWriteLock_? – nosid May 25 '14 at 14:24
  • @nosid: Edited the question - I mention the lock because it exists to coordinate the readers/writers and it might make a difference - but the question is not about the lock just about necessary synchronization on accessing the fields of the mutable object – Mr_and_Mrs_D May 25 '14 at 14:47
  • You might want to make that inner class static for memory efficiency. – David Ehrmann May 25 '14 at 16:20
  • @DavidEhrmann: It's one of the rare uses of non static inner classes this (in my design at least) - the nodes must know the tree (for the record it's a disk based b+ tree) – Mr_and_Mrs_D May 25 '14 at 16:22

2 Answers2

12

No.

Placing a reference to an object in a volatile field does not affect the object itself in any way.

Once you load the reference to the object from the volatile field, you have an object no different from any other object, and the volatility has no further effect.

SLaks
  • 868,454
  • 176
  • 1,908
  • 1,964
  • Hmm reads and writes to a volatile filed do establish a happens before relation. Also what about the other parts of the question ? – Mr_and_Mrs_D May 25 '14 at 13:25
  • 13
    So that means marking a reference to an object `volatile` only makes sure that every thread gets the correct object, but when the threads try to read the properties of that object, they may still get stale values because the properties on that object are not `volatile`. Is that correct? – Wang Sheng Apr 07 '15 at 10:35
  • to make the matters worse ;) what happens if that referenced object has volatile and non-volatile fields. Do volatile fields of that object get updated value and non-volatiles not. So, this would make the object state inconsistent :| – Mr.Q Feb 17 '19 at 06:10
  • 1
    @Mr.Q the state will be inconsistent, regardless of whether the object’s fields are declared `volatile` or not. When none of the fields has been declared `volatile`, it is still possible to read up-to-date values for some of them, while reading stale values for others. When all of them are `volatile`, reading them will give more recent values, but there’s still no guaranty that all values are from the same update, e.g. when the fields are read while another thread is in the middle of an update. The only scenario with guaranteed consistency is when there is only a single `volatile` field. – Holger May 09 '22 at 13:01
9

The are two question. Let's start with the second.

Assigning newly constructed objects to volatile variables works nicely. Every thread, that reads the volatile variable, will see a fully constructed object. There is no need for further synchronization. This pattern is usually seen in combination with immutable types.

class Tree {
    private volatile Node node;
    public void update() {
        node = new Node(...);
    }
    public Node get() {
        return node;
    }
}

Regarding the first question. You can use volatile variables to synchronize access to non-volatile variable. The following listing shows an example. Imagine that the two variables are initialized as shown, and that the two methods are executed concurrently. It is guaranteed, that if the second thread sees the update to foo, it will also see the update to bar.

volatile int foo = 0;
int bar = 0;

void thread1() {
    bar = 1;
    foo = 1; // write to volatile variable
}

void thread2() {
    if (foo == 1) { // read from volatile variable
        int r = bar; // r == 1
    }
}

However, your example is different. The reading and writing might look as follows. In contrast to the above example, both threads read from the volatile variable. However, read operations on volatile variables do not synchronize with each other.

void thread1() {
    Node temp = root; // read from volatile variable
    temp.numOfKeys = 1;
}

void thread2() {
    Node temp = root; // read from volatile variable
    int r = temp.numOfKeys;
}

In other words: If thread A writes to a volatile variable x and thread B reads the value written to x, then after the read operation, thread B will see all write operations of thread A, that occurred before the write to x. But without a write operation to a volatile variable, there is no effect on updates to other variables.


That sounds more complicated than it actually is. Actually, there is only one rule to consider, which you can find in JLS8 §17.4.5:

[..] If all sequentially consistent executions are free of data races, [..] then all executions of the program will appear to be sequentially consistent.

Simply put, a data race exists if two threads can access the same variable at the same time, at least one operation is a write operation, and the variable is non-volatile. Data races can be eliminated by declaring shared variables as volatile. Without data races, there is no problem with visibility of updates.

nosid
  • 48,932
  • 13
  • 112
  • 139