Question

Shouldn't it be myS.equals("/usr") in this explanation from the JLS?

Final fields are designed to allow for necessary security guarantees. Consider the following example. One thread (which we shall refer to as thread 1) executes

Global.s = "/tmp/usr".substring(4);

while another thread (thread 2) executes

String myS = Global.s;
if (myS.equals("/tmp"))System.out.println(myS);

String objects are intended to be immutable and string operations do not perform synchronization.

Was it helpful?

Solution

Actually no, that might be what you'd think at first glance, but that is intentional. Quoting further down the text (emphasis mine):

[...] if the fields of the String class were not final, then it would be possible (although unlikely) that thread 2 could initially see the default value of 0 for the offset of the string object, allowing it to compare as equal to "/tmp"

OTHER TIPS

They are describing a hypothetical situation in which the returned substring might appear to be either /tmp or /usr because of a race condition. As a result it doesn't really matter which string is used for the comparison; the point of the example is that either could possibly be correct if the conditions described in this example held.

I do not see anything wrong. Both the code snippets points to different thread and example is taken so as to justify the truly immutability of String class in java. Earlier, the value of the string was "/ THREADS...." when thread 2 was executing and then it was changed to "/usr" by thread 1. It is clearly explained in the description.

No it should not.

This example is a good one because it refers to a bug that was present in some JVM implementations (unfortunately I don't remember which). String.substring did not create a new string but rather pointed to the old one with non-final offset and length fields pointing to the correct location. However, as the fields were not final (and there was no other synchronization) what could happen is exactly the scenario mentioned in the paragraph that follows the example code:

In particular, if the fields of the String class were not final, then it would be possible (although unlikely) that thread 2 could initially see the default value of 0 for the offset of the string object, allowing it to compare as equal to "/tmp". A later operation on the String object might see the correct offset of 4, so that the String object is perceived as being "/usr".

So in fact while Strings were said to be immutable, they were not because objects can be visible to other threads before their constructors are fully run. And the example illustrates just that.

Strings 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.)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top