简体   繁体   中英

Final field semantics & deserialization with setAccessible(true)

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!

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.

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:

  • During deserialization, when the object memory space is allocated, but before the final attribute is assigned a value
  • During a modification of a final field via the Reflection API (ie 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:

  • At the end of the execution of a constructor, in which an undefined final field is actually assigned a value
  • After a final field value change, via something like the Reflection API

The 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 (ie 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.

From JLS

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.

since the memory effect is defined only in term of freeze action, this implies that semantics also applies if a final field is modified after constructor - as long as the object reference isn't leaked prior to that. This is considered a legitimate use case.

As soon as the object reference is published, further modification on a final field is not a good idea.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM