reference was written. For example, this means that if one thread constructs an object and uses methods on
that object to set its state, then a second thread will see the state set by the first thread if the reference used to
access the object was written to a final field after the state changes were made. Any final fields in such objects
are covered by the first rule.
To preserve the integrity of the system, classes must use final fields appropriately to protect sensitive data, if
correct synchronization cannot be relied on. For this reason, the String class uses final fields internally so
that String values are both immutable and guaranteed to be visible.
14.10.3. The Happens-Before Relationship
The description of synchronizations actions above is a simplification of the operation of the memory model.
The use of synchronized blocks and methods and the use of volatile variables provide guarantees concerning
the reading and writing of variables beyond the volatile variables themselves or the variables accessed within
the synchronized block. These synchronization actions establish what is called a happens-before relationship
and it is this relationship that determines what values can be returned by a read of a variable. The
happens-before relationship is transitive, and a statement that occurs ahead of another in program order
happens-before that second statement. This allows actions other than synchronization actions to become
synchronized across threads. For example, if a non-volatile variable is written before a volatile variable, and
in another thread the same volatile variable is read before reading the non-volatile variable, then the write of
the non-volatile variable happens-before the read of that variable and so the read is guaranteed to return the
value written. Consider the following:
If the read of dataReady by thread-2 yields true, then the write of dataReady by thread-1
happened-before that read. Since the write to data by thread-1 happens-before the write to dataReady by
thread-1, it follows that it also happens-before the read of dataReady by thread-2, hence the read of data
by thread-2. In short, if thread-2 sees dataReady is true then it is guaranteed to see the new Data object
created by thread-1. This is true because dataReady is a volatile variable,[2] even though data is not itself
a volatile variable.
[2] The same would be true if dataReady were not volatile but instead had synchronized
get and set methods.
Finally, note that the actual execution order of instructions and memory accesses can be in any order as long
as the actions of the thread appear to that thread as if program order were followed, and provided all values
read are allowed for by the memory model. This allows the programmer to fully understand the semantics of
the programs they write, and it allows compiler writers and virtual machine implementors to perform complex
optimizations that a simpler memory model would not permit.
This discussion gives you an idea of how the memory model interacts with multithreading. The full details of
the memory model are beyond the scope of this book. Fortunately, if you pursue a straightforward locking