Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cheapest way of establishing happens-before with non-final field

Many questions/answers have indicated that if a class object has a final field and no reference to it is exposed to any other thread during construction, then all threads are guaranteed to see the value written to the field once the constructor completes. They have also indicated that storing into a final field a reference to a mutable object which has never been accessed by outside threads will ensure that all mutations which have been made to the object prior to the store will be visible on all threads which access the object via the field. Unfortunately, neither guarantee applies to writes of non-final fields.

A question I do not see answered, however, is this: If the semantics of a class are such that a field cannot be final, but one wishes to ensure the "publication" of the field and the object identified thereby, what is the most efficient way of doing that? As an example, consider

class ShareableDataHolder<T>
{
  Object data; // Always identifies either a T or a SharedDataHolder<T>
}
private class SharedDataHolder<T> extends ShareableDataHolder<T>
{
  Object data; // Always identifies either a T or a lower-numbered SharedDataHolder<T>
  final long seq; // Immutable; necessarily unique
}

The intention would be that data will initially identify a data object directly, but that it could legitimately at any time be changed to identify a SharedDataHolder<T> which directly or indirectly encapsulates an equivalent data object. Assume all code is written to work correctly (though not necessarily optimally-efficiently) if any read of data may arbitrarily return any value that was ever written to data, but may fail if it reads null.

Declaring volatile Object data would be semantically correct, but would likely impose extra costs on every subsequent access to the field. Entering a dummy lock after initially setting the field would work, but would be needlessly slow. Having a dummy final field, which the object sets to identify itself would seem like it should work; although technically I think it might require that all accesses to the other field be done through the other field, I can't see any realistic scenario where that would matter. In any case, having a dummy field whose purpose is only to provide the appropriate synchronization via its existence would seem wasteful.

Is there any clean way to inform the compiler that a particular write to data within the constructor should have a happens-before relationship with regard to any reads of that field which occur after the constructor returns (as would be the case if the field were final), without having to pay the costs associated with volatile, locks, etc.? Alternatively, if a thread were to read data and find it null, could it somehow repeat the read in such a fashion as to establish a "happens after" with regard to the write of data [recognizing that such a request might be slow, but shouldn't need to happen very often]?

PS--If happens-before relationships are non-transitive, would a proper happens-before relationship exist in the following scenario?

  1. Thread 1 writes to a non-final field dat in some object Fred and stores a reference to it into to a final field George.
  2. Thread 2 copies the reference from George into a non-final field Larry.
  3. Thread 3 reads Larry.dat.

From what I can tell, a happens-before relationship exists between the write of Fred's field dat and a read of George. Would a happens-before relationship exist between the the write of Fred's dat and a read of Larry that returns a reference to Fred that was copied from a final reference to Fred? If not, is there any "safe" way to copy a reference contained in a final field to a non-final field that would be accessible via other threads?

PPS--If an object and its constituents are never accessed outside their creation thread until the main constructor finishes, and the last step of the main constructor is to stores within the main object a final reference to itself, is there any "plausible" implementation/scenario where another thread could see a partially-constructed object, whether or not anything actually uses that final reference?

like image 328
supercat Avatar asked Mar 20 '23 03:03

supercat


1 Answers

Short answer

No.

Longer answer

JLS 17.4.5 lists all* of the ways of establishing a happens-before relationship, other than the special case of final field semantics:

  1. An unlock on a monitor happens-before every subsequent lock on that monitor.
  2. A write to a volatile field (§8.3.1.4) happens-before every subsequent read of that field.
  3. A call to start() on a thread happens-before any actions in the started thread.
  4. All actions in a thread happen-before any other thread successfully returns from a join() on that thread.
  5. The default initialization of any object happens-before any other actions (other than default-writes) of a program.

(The original lists them as bullet points; I'm changing them to numbers for convenience here.)

Now, you've ruled out locks (#1) and volatile fields (#2). Rules #3 and #4 relate to the life-cycle of the thread, which you don't mention in your question, and doesn't sound like it would apply. Rule #5 doesn't give you any non-null values, so it doesn't apply either.

So of the five possible methods for establishing happens-before, other than final field semantics, three don't apply and two you've explicitly ruled out.


* The rules listed in 17.4.5 are actually consequences of the synchronization order rules defined in 17.4.4, but those relate pretty directly to the ones mentioned in 17.4.5. I mention that because 17.4.5's list can be interpreted as being illustrative and thus non-exhaustive, but 17.4.4's list is non-illustrative and exhaustive, and you can make the same analysis from that directly, if you don't want to rely on the intermediate analysis that 17.4.5 provides.

like image 52
yshavit Avatar answered Mar 22 '23 15:03

yshavit