[concurrency-interest] Should I avoid compareAndSet with value-based classes?

Gil Tene gil at azul.com
Tue Jul 11 14:22:51 EDT 2017


> On Jul 11, 2017, at 10:30 AM, Aleksey Shipilev <shade at redhat.com> wrote:
> 
> On 07/11/2017 06:22 PM, Gil Tene wrote:
>> To test the notion that == "means what you think it means" in some places.
>> Lets take your example a couple of steps farther:
> 
>> Class AnnotatedTime {
>>      String annotation;
>>      long secondsSinceEpoch;
>>      long nanosInSecond;
>>      Instant instant;
>> 
>>      boolean isExactSameInstant(Instant other) {
>>           return (instant == other);
>>      }
>> }
>> 
>> HashMap<Long, Instant > instanceCache = new HashMap<>();
>> 
>> Instant i1 = Instance.ofEpochSecond(1000);
>> Instant i2 = Instance.ofEpochSecond(1000);
>> 
>> AnnotatedTimes annotatedTime = new AnnotatedTime();
>> annotatedTime1.annotation = "Time at 1000"; 
>> annotatedTime1. secondsSinceEpoch = 1000;
>> annotatedTime1. instant = i1;
>> 
>> instanceCache.put(1000L, i1);
>> 
>> A. (i1 == i2)  // We all agree it may be true or false depending on implementation details
> 
> Yup.
> 
>> B. (i1 == i1)  // You say it must remain true. I say it could validly be
>> false. This is a computation of a boolean expression.
> I assume you have a highly-paid language lawyer on retainer than could justify
> this :) I do think that referential equality stays reflexive, no matter the
> nature of reference you are comparing.
> 
> The reflexivity seems to come from reference equality operator definition in
> 15.21.3: "At run time, the result of == is true if the operand values are both
> null or both refer to the same object or array; otherwise, the result is false".
> I have a hard time coming up with a definition of "same" that is not reflexive.
> Can you do it?

identityHash and synchronized have similarly strong behavior expectations when performed on the same object. If we say that the notion of "same object" applies to instances value based classes when evaluating ==, are we also saying that it also applies to synchronized and identityHash? [It must be. What's the alternative?]

> 
> The only way out from here is to claim that either == is not reference
> comparison, or there is some sort of conversion is taking place before ==.

Value-purists might argue that == on a value based class is not a reference comparison because a field "referring" to an instance of value based class is not a reference. I'm not quite that pure tho.

I'd rely on ""Instances of a value-based class… are freely substitutable when equal, meaning that interchanging any two instances x and y that are equal according to equals() in any computation or method invocation should produce no visible change in behavior.".

So while you have a reference to an object that is an instance of a value based class, any use of that reference can include replacing the value you just read from the [field or variable] with some other (potentially !=) logical reference that is .equals() to the one being replaced. There is no way to write a == expression on references to value based objects and know that the references are to "the same object", since the value on either side of the == operations can be freely substituted with something else before the == is evaluated. Same for when you store a value in a field (what you store could be the substituted value), or pass a parameter to a method (what you pass could be substituted). This consistently applies to identityHash() (the "this" passed to identityHash() could be substituted) and the synchronized(instant)...

I seem to claim that instances of value based classes can be freely substituted (with a .equals() instance) in pretty much any expression that explicitly operates on a value based class type. [I also separately think that when expressions operate purely on Object types (and not explicitly on a value based class type) this substitution is not allowed,  even when the Object instance happens to be a value based class. The two statements can validly co-exist IMO]. 

You seem to claim that substitution is not allowed in expressions (in spite of spec statement), but that separately executing expressions that create new instances with the "same" (.equals()) value do not have to produce the same (==) object instance.

So, are there expressions on explicitly-typed-as-value-based-class things where instance substitution is not allowed in spite of the value-based class specification statements? (Your answer will probably be "still not allowed in ANY expressions", my answer is "I think it's allowed in ALL expressions as long as the type is explicitly a value based class". Maybe there are some where is and some where it isn't?)

> This
> is the case for auto-boxed primitives, for example. For Instant case, you cannot
> claim either: it is a reference type, and reference comparisons do not need
> conversions.
> 
> I guess "real" value types, if implemented, would experience this:
> 
> MyValueType v1; // <--- *not* a reference type
> 
> // if "==" is a value comparison, should pass
> // if "==" is a reference comparison, *and* prior boxing, may fail
> assert(v1 == v1);
> 
> In fact, spec may be allowed to produce new instance on every single boxing.
> Which will render reference CAS -- that requires boxing -- useless, if that what
> OP originally asked.
> 
> But, there are no non-reference types yet, except for primitives, which makes
> this construction moot for all Java's up to and including 9.
> 
>> C. (i1 == annotatedTime. instant) // You say this must be true as well??
> 
> Yes, because it compares the reference to the same object.
> 
>> D. (annotatedTime1.isExactSameInstant(i1)) // Is this required to be true?
> 
> Yes, because it compares the reference to the same object.
> 
>> E. (i1 == instanceCache.get(1000L)) // And this must also be true??
> 
> Yes, because it compares the reference to the same object.
> 
> 
>> If you say that B,C,D,E must all be true (which would presumably be
>> self-consistent), you are basically saying that even tho the spec says so,
>> Instant cannot be freely replaced with other Instants that are .equals() with
>> it in computations and method invocations. At that point, nothing
>> distinguishes a value-based class from a regular class that is immutable.
>> E.g. Instant and Integer have the same defined behavior according to this
>> interpretation, and all mention of a value-based class in the spec is
>> basically a no-op.
> 
> My interpretation is that "value-based class" today is the property of the
> producing expressions -- Instant.ofEpochSecond in this case -- extended to every
> producing expression (whether it is a language construct, factory call, etc)
> that can give the instance.
> 
> 
>> Note that long-standing immutable classes with clean .equals() and hashcode
>> behaviors (like Integer, Long, etc.) have not been specified to be
>> value-based classes. Presumably due to historical compatibility reasons,
>> since the identity of instances of those classes certainly carries meaning,
>> and B-E above would all have to be true for them. In contrast, all current
>> classes that are defined as value-based classes have been defined so from
>> their inception. If you want to interpret the specification of value-based
>> classes as not contradicting the behavior of e.g. Integer, why the
>> definition? And why is Integer NOT a value-based class?
> I think "value-based class" language is provisional for "do not rely on
> identity, even if it is defined for the objects you will be getting". Integer,
> as you say, have already leaked the identity in public, and we are done there,
> the guy is disqualified.
> 
> In this parlance, saying "Instant is like primitive/Integer" is far worse
> language than actually spelling out the "unknown identity" rules with
> "value-based class" text. Having these rules spelled out eases switching them to
> real value types, with appropriate spec changes. But, it does not give a blanket
> approval for doing so, it just minimizes the damage.
> 
> I understand one could claim "JVM is allowed to ignore identity for value-based
> classes, because it is claimed to be unpredictable anyway", and probably lawyer
> up to say it is spec-legal. Although it would be fun to see the defense where
> Javadoc trumps the core JLS.
> 
> Still, I think the interpretation of "identity stays unknown, but still behaves
> like an object" is saner from quality of implementation, principle of least
> astonishment, "don't break Java" standpoints.
> 
> How will Valhalla deal with these problems, we shall see. My bet is on
> value-type-specific comparisons (which should catch CAS in its wake), plus
> boxing where the references are needed (which would capitalize on what we
> learned to love about Java's primitives).
> 
> I think this CAS example is interesting to discuss in valhalla-dev@, where it
> actually matters, not here.
> 
> Thanks,
> -Aleksey
> 
> 
> 



More information about the Concurrency-interest mailing list