Each thread has its own Java stack. The Java stack is a sequence of stack frames. Each stack frame represents a method call, with its own program counter, local variable array, and operand stack. The local variable array and operand stack are not shared between threads, so there is no need to synchronize on them.
All threads share the same heap. Threads may attempt to use the same object at the same time. If only one thread can be allowed to work with an object, as the ATM example discussed earlier shows, then it may be necessary to lock the object. The JVM provides instructions to enable threads to lock objects (discussed in section 16.3).
Even if the object is not locked, the JVM still provides some protection for the fields of the object. Reading and writing fields (with getfield/putfield or getstatic/putstatic) are atomic operations; that is, they are indivisible and cannot be interrupted by switching between threads. It is impossible to read a field in one thread while another thread writes to it, and vice versa. It is also impossible for two threads to write to a field at the same time. (This is not true for long or double values; see section 16.2.2).
16.2.1 Need for Synchronization
To see the sort of havoc that could be caused by the simultaneous read and write of a value, suppose that there is an object with an int field that should contain either +1 or 1. There are two threads, one of which reads the field with getfield, the other of which writes to the field with putfield. In Figure 16.4, each square represents one byte of the 32-bit int value, which originally contains +1. One thread wants to change it to 1 at the same time another thread wants to read the value. At the point the reader reads the thread, the writer is only half done with its work: only the left two bytes have been written. The value is nonsensical: it is interpreted as 65,535, which is neither 1 nor 1, even though no thread intended to write such a nonsensical value.
Figure 16.4. Perils of improper synchronization
The JVM specification forbids this from happening. Once a thread begins to write, it must complete the operation before anybody else is allowed to read, even if the object has not explicitly been locked. What happens is shown in Figure 16.5. Because the reader is blocked from reading the field while the writer is setting the field value, the reader never sees the nonsense value that occurs halfway through writing. Instead, it sees only the final value.
Figure 16.5. Synchronization preserves integrity
If both the reader and the writer happen at precisely the same time, or if two writers try to set a field at precisely the same time, the JVM is responsible for picking one or the other and letting it proceed while blocking the other. It does not matter which one is picked to proceed, only that exactly one proceeds at a time.
For example, suppose both read and write occur at the same time and the reader thread is picked to go first (Figure 16.6). The writer has been blocked from proceeding until the reader has finished getting the value of the field. This means that the reader reads the value of the field before it is set. Immediately after that, the value changes. The JVM could just as easily choose the writer thread to proceed instead of the reader thread, in which case the reader would read the new value of the field.
Figure 16.6. Reader goes first
One thread may require that the field not change its value until it has performed some more complex operation. For example, the value of this field may be linked to the value of another field, and both of them must be set at the same time without any intervening read operations. The thread must communicate to the other threads that it wishes exclusive access to the object. For this reason, the JVM provides monitors and object locks, as discussed in section 16.3.
16.2.2 Exception: longs and doubles
There is an exception to this rule: long and double fields may be read and written as two separate atomic operations. Many systems provide hardware support for atomic reads and writes of 32-bit values, but not for the 64-bit values required by long or double values. This can lead to the sort of error shown in Figure 16.4.
If you write a program in which objects containing double-word values are shared between threads, it is critical that you use locks to ensure that this sort of half-written value doesn't occur.