[concurrency-interest] Re: AtomicInteger and AtomicLong should implement Number

Dawid Kurzyniec dawidk@mathcs.emory.edu
Wed, 14 Jan 2004 21:53:46 -0500


> -----Original Message-----
> From: concurrency-interest-admin@cs.oswego.edu 
> [mailto:concurrency-interest-admin@cs.oswego.edu] On Behalf 
> Of Larry Riedel
> Sent: Wednesday, January 14, 2004 4:42 PM
> To: concurrency-interest@altair.cs.oswego.edu
> Subject: [concurrency-interest] Re: AtomicInteger and 
> AtomicLong should implement Number
> 
> To me it seems intuitively pretty clear that introducing a 
> facility which adds atomic access semantics to an entity does 
> not justify changing the way that entity is compared with 
> other entities of that type.  So I think if the manifestation 
> of the facility is to have a class whose instances are 
> surrogates for the entities, the surrogates should, to the 
> extent feasible, try to assume the type and comparison 
> semantics of those entities.  

I really like this argument, and I agree with it. The point where we
fail to agree is whether atomic classes are indeed surrogates for the
values they hold. I personally think they aren't (in other words, that
we have the "contains" rather than "is" relation here). It seems to be a
matter of taste, and opinions vary depending on background and earlier
experiences people have. Nevertheless, let me try to explain my point of
view a bit further. It is interesting to hypotetically consider what
would happen if the atomicity was added at the language level (as, I
suppose, many of us would like it to be). Let's assume that we have the
"atomic" field modifier and a hash map which accepts primitives as keys.
Then let's consider the following code:

class A {
  atomic int a;
  Map<String> m = new HashMap<String>();

  void foo() {
    a = 5;
    m.put(a, "5");
    m.put(6, "6");
    a++;
    String val5 = m.get(5);
    String val6 = m.get(a);
  }
}

Quick question before you read on: what would you expect val5 and val6
to be?

Here, the value of a variable which was used as a key is being changed
after using it as a key. Nevertheless, it seems natural to expect that
the code works, e.g. that the val5 gets assigned "5", and that val6 gets
assigned 6. In other words, it seems natural to assume that the mapping
is created by taking the momentary value of the variable rather than the
variable itself as a key. In yet another words, it seems natural to
expect to observe the behavior defined in JLS 15.2: "If an expression
denotes a variable, and a value is required for use in further
evaluation, then the value of that variable is used."

The equivalent code written using atomic classes is as follows:

class A {
  AtomicInteger a = new AtomicInteger();
  Map<String> m = new HashMap<String>();
  
  void foo() {
    a.set(5);
    m.put(a.get(), "5");
    m.put(6, "6");
    a.incrementAndGet();
    String val5 = m.get(5);
    String val6 = m.get(a.get());
  }
}

Note that I had to use "get()" twice in order to obtain the same
behavior as before. Otherwise, the variable itself would be taken as a
key, and the behavior of a Map would become undefined. It is as if
somehow in the previous example the "field pointer" was taken as a key
rather than a value of the variable.

In other words, it follows that "get()" is semantically equivalent to
the operation performed by Java runtime on the ordinary variable when it
is used in an expression (other than at the right hand side of an
assignment expression). Hence, I don't consider the neccessity to use
"get()" as a bad thing - it is simply reflecting the fact that the value
of the variable is being used rather than the variable itself. The lack
of the distinction between a variable and its value seems to be driving
this discussion.

> Also, I think if the phrase "at the same apparent relative 
> location inside the JVM" was used instead of "identity-based 
> equality" to describe the use of the "==" operator on a pair 
> of Java reference values (JLS 4.3), it might do a better job 
> of making it clear that using Java "==" as an equivalence 
> operator for identity may have little or no intuitive high 
> level meaning.

I'd say that this issue is a bit of a red herring here. JSR 166 group
has a well-defined goals, which do not include changing the "feel" or
the fundamentals of Java language. But I will actually try to defend the
meaningfulness of using "==" as an identity operator:

The Java heap contains Java objects. Objects may have references to
other objects. Thus, objects constitute a graph. Of course, at the lower
level you have some flat memory and "relative locations", but the JLS is
not concerned with such a low level. From the JLS point of view, the
heap simply contains objects constituting a graph. The objects in Java
are very distinguishable, concrete entities, and the "==" relation tests
whether two (usually distinct) references point to the same object in
the heap. As a consequence of their concretness, the most fine-grained
equivalence relation you may have in the set of Java objects is the one
where every single object constitutes an individual equivalence class.
This canonical equivalence relation is defined by the default
Object.equals() implementation. By overriding equals(), you can redefine
the equivalence relation to group multiple objects into equivalence
classes, but you cannot get any finer than Object.equals(), which makes
this one canonical and a natural default.

>From this point of view, and if you agree that atomics are variables,
there is nothing wrong with leaving atomics with default "equals()" --
it is OK since every two distinct atomic objects are distinct variables,
e.g. they do not refer to the same memory location. 

I would compare the atomic classes to java.lang.reflect.Field, which
also represents a variable. Would you argue that Field is a surrogate
for a value it represents? Would you expect Field to have value-based
equals? In fact, Field equals() is redefined to determine whether two
"Field" objects refer to the same named field in the same class; i.e.
the "equals" mean that they obtain their values from the same memory
offset given the object instance. Since every atomic class has its own
memory location, two atomics are always non-"equal" from this
perspective unless they are the same object in terms of "==".

Kind regards,
Dawid Kurzyniec