String
s internally a character array with an offset and a length. This character array used to be reused between multiple strings. As a memory use optimization, substring()
would return a new String
backed by the same character array as the original string. What this method does is determine what the new offset and length into this character array for the result string would be, then call a private constructor to create this result object and return it.
(As Joachim points out, this is no longer the way String
works, but the example in the JLS is based on the older internals.)
Now, what the implementation of that private constructor, instruction reordering by the JIT or the CPU, or just generally the wacky way in which memory shared between threads works could cause is that this new String
object would first have its length set to 4
. The offset would remain at 0
, making the value of this String
"/tmp"
. Only a split-second later would its offset be set to 4
, and make its value correctly be "/usr"
.
To put it another way: thread 2 would be able to observe this string in the middle of its constructor executing. This seems counterintuitive because people intuitively understand the code of thread 1 as first fully executing the right-hand-side of the assignment, and only then changing the value of Global.s
. Unfortunately, without appropriate memory synchronisation, other threads are able to observe a different sequence of events. Using final
fields is one way to make the JVM handle this correctly. (I believe declaring Global.s
as volatile
would also work, but I'm not 100% sure.)