17.11 Example: Out-of-Order Writes

This example is similar to that in the preceding section, except that one method assigns to both variables and the other method reads both variables. Consider a class that has class variables a and b and methods to and fro:


class Simple {
	int a = 1, b = 2;
	void to() {
		a = 3;
		b = 4;
	}
	void fro() {
		System.out.println("a= " + a + ", b=" + b);
	}
}

Now suppose that two threads are created, and that one thread calls to while the other thread calls fro. What is the required set of actions and what are the ordering constraints?

Let us consider the thread that calls to. According to the rules, this thread must perform an assign of a followed by an assign of b. That is the bare minimum required to execute a call to the method to. Because there is no synchronization, it is at the option of the implementation whether or not to store the assigned values back to main memory! Therefore the thread that calls fro may obtain either 1 or 3 for the value of a, and independently may obtain either 2 or 4 for the value of b.

Now suppose that to is synchronized but fro is not:


class SynchSimple {
	int a = 1, b = 2;
	synchronized void to() {
		a = 3;
		b = 4;
	}
	void fro() {
		System.out.println("a= " + a + ", b=" + b);
	}
}

In this case the method to will be forced to store the assigned values back to main memory before the unlock action at the end of the method. The method fro must, of course, use a and b (in that order) and so must load values for a and b from main memory.

The total set of actions may be pictured as follows:

Here an arrow from action A to action B indicates that A must precede B.

In what order may the actions by the main memory occur? Note that the rules do not require that write a occur before write b; neither do they require that read a occur before read b. Also, even though method to is synchronized, method fro is not synchronized, so there is nothing to prevent the read actions from occurring between the lock and unlock actions. (The point is that declaring one method synchronized does not of itself make that method behave as if it were atomic.)

As a result, the method fro could still obtain either 1 or 3 for the value of a, and independently could obtain either 2 or 4 for the value of b. In particular, fro might observe the value 1 for a and 4 for b. Thus, even though to does an assign to a and then an assign to b, the write actions to main memory may be observed by another thread to occur as if in the opposite order.

Finally, suppose that to and fro are both synchronized:


class SynchSynchSimple {
	int a = 1, b = 2;
	synchronized void to() {
		a = 3;
		b = 4;
	}
	synchronized void fro() {
		System.out.println("a= " + a + ", b=" + b);
	}
}

In this case, the actions of method fro cannot be interleaved with the actions of method to, and so fro will print either "a=1, b=2" or "a=3, b=4".