According to the Java Memory Model, a final
field initialized in the object's constructor not subject to further modifications is guaranteed to have its value correctly seen by every thread reading it, even if the object itself has been published with data races.
The JLS talks about 17.5.3 Subsequent Modification of Final Fields, and vaguely states that
An implementation may provide a way to execute a block of code in a final field safe context.
It does not seem to really define the semantics of such modifications, nor where exactly this final field safe context thing must exist or how to define it (ie, the JLS doesn't seem to give any guarantee about subsequent modification of final fields).
I must say that I did not fully understood the partial orders dereferences() and mc(), nor the behavior of the freeze action that takes place after any modification to a final field (either the initial value attributed to it or subsequent modifications).
On this context, what I want to know is: how can a (de)serialization framework, such as Gson, guarantee that deserialized objects containing final fields properly initialized in a constructor will pose no thread visibility problem?
For example, consider this class:
class X {
private final String s;
public X(final String s) { this.s = s; }
@Override public String toString() { return s; }
}
And the following code:
final Gson gson = new Gson();
X x = gson.fromJson(gson.toJson(new X("abc")), X.class);
System.out.println(x);
// prints abc
Stepping into the method fromJson
with a debugger, I see that sun.misc.Unsafe
is used to allocate an instance of X
without calling its constructor, and the fields are setAccessible(true)
, and finally they get set.
And this is only in Sun's (or compatible) JVMs! It looks like Gson has code specific to multiple Android versions as well.
So, are there any thread-safety guarantees associated with these deserialized final fields, just like I would have with an instance of X
constructed with new X("abc")
? If so, where does this guarantee come from?
Thanks!
Thread safety
As I read it, the thread safety guarantee comes from the fact that a given attribute is declared as final. The point at which it is NOT thread safe, is either:
final
attribute is assigned a valuefinal
field via the Reflection API (i.e. while the value is in the process of being modified, and before it's done with this process)The caveat here is that the reference you linked to allows the theoretical possibility of something other than the Reflection API (but with the same final
field modification abilities) to exist.
Freezing
Freezes of a final field occur both at the end of the constructor in which the final field is set, and immediately after each modification of a final field via reflection or other special mechanism.
As far as the freezing calls, basically it's saying that freeze
is used to mark an attributes as "this cannot be changed", and it does so:
final
field is actually assigned a valuefinal
field value change, via something like the Reflection APIThe thread-safety concern only applies to the object being modified/deserialized.
So:
(...code that comes before...)
END final field safe context - entering non-threadsafe area
final fields are not frozen
code that deserializes an object OR modifies a final field via reflection
final fields are re-frozen
RESUME final field safe context - re-entering threadsafe area
(...code that comes after...)
Therefore...
After a constructor runs (i.e. you've got an instantiated object with values assigned), the final field cannot be altered, because it is frozen, except in the case where the Reflection API is used to directly alter that value - after which it is frozen again because, after all, the field is declared final.
If you're looking for an Android-specific answer to this (to ensure that its JVM behavior agrees with Sun's/Oracle's JVM), you'll need to find equivalent documentation for that JVM.
The unfreezing comes along with the (java.reflect.)Field.setAccessible(true) call. Most frameworks using reflection regularly to set final fields often never call field.setAccessible(false) after a successful modification so leaving this field 'unfrozen'.
So any other reflection framework in charge will see that the field is accessible and might do so. This is a theoretical chance but it might occure. There is a reason why such kind of operation inside the serialization mechanism is using a method of the class Unsafe (sun implementation package).
So when using any kind of a reflection API you can really mess up a JVM and cause any kind of unpredictable behaviour. The only thing that saves the default deserialization mechanism is that it is done on instance creation time, where no chance exist that there will be any concurrent access happening.
Thats why such kind of access can be restricted or even forbidden by a SecurityManager.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With