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

Dawid Kurzyniec dawidk@mathcs.emory.edu
Mon, 12 Jan 2004 23:09:25 -0500

> -----Original Message-----
> From: concurrency-interest-admin@cs.oswego.edu 
> [mailto:concurrency-interest-admin@cs.oswego.edu] On Behalf 
> Of Larry Riedel
> Sent: Monday, January 12, 2004 7:47 PM
> To: concurrency-interest@altair.cs.oswego.edu
> Subject: [concurrency-interest] Re: AtomicInteger and 
> AtomicLong should implement Number
> > > I would say it would, regardless of anything to do with atomic vs 
> > > not atomic, be an inauspicious decision to use an object 
> as a key in 
> > > a hashtable during a period of time when its value as a key might 
> > > change during the normal/expected course of events in the 
> > > application.
> > 
> > I have said it before: It HAS to do with atomic, since they are 
> > provided SPECIFICALLY to support unsynchronized concurrent 
> > modifications.
> When I invoke compareAndSet(), I expect it to be functionally 
> equivalent to if its implementation did indeed do Java 
> language level synchronization; I would use the 
> java.util.concurrent.Atomic* version because it is 
> (presumably) implemented to do that orders of magnitude more 
> quickly than if it used Java language level synchronization 
> because it is a particular scenario which has [hardware] support.

The atomicity provided by Atomic* classes is not sufficient alone to
allow you to rely on equals() or Comparable. When you use comparisons
(e.g. when you sort or when you use maps), you have to ensure that the
value does not change during the whole operation, i.e. the whole thing
from comparing to putting the value into the hash table to using it and
removing it, or the whole "sort" operation, must be performed while the
value does not change. Am I stating the obvious here?...

> I imagine something like the following:  I have an object 
> whose state is queried often, maybe from one thread, maybe 
> from another, and on average the state of the object changes 
> relatively seldom and relatively predictably 
> (deterministically), or maybe it never changes unless 
> something unusual happens.  When a decision is made to change 
> the state of the object, that decision and the new state are 
> based in part on what the existing state is believed to be.  
> There is no single thread dedicated to (or with an exclusive 
> mandate for) changing the state. When the state does change, 
> it must change directly from one correct state to another.
> For this particular application it may be very unlikely or 
> almost inconceivable that between the time a thread queries 
> the state of the object and commands the object to change its 
> state, some other thread commands the object to change state, 
> but it COULD happen, and the atomicity of that pair 
> (query+command) of operations needs to be ensured to preserve 
> the invariants.  If for this application the query+command 
> pair fits the "compareAndSet" paradigm of Atomic*, I would 
> like to use that, because it seems like a simple/easy way to 
> get good performance.

No problem with that. No need for hashCode() or comparable so far, too.

> Besides the frequent querying of the object state, there are 
> various things the application does which leverages the fact 
> that the state of the object seldom (if ever) changes, by 
> assuming the state will be constant during a sequence of 
> operations, or maybe it is virtually guaranteed to take place 
> between state changes because the completion of the sequence 
> of operations is one of the logical criteria affecting the 
> decision to change the state of the object.  

If you know that the value will not change, you could (and should) use
the momentary value obtained by .get() as a hashtable key, as Doug Lea

Also, this actually looks to me more like a scenario for an
identity-based key rather than value-based key, i.e. why would you map
something to the volatile state rather than to the object itself?... But
nevertheless, even if it was indeed something you wanted to do, use
get() in this case.

> Or maybe for 
> some things the application does not mind if the state 
> changes and, for example, it cannot find an object in a 
> hashtable-- maybe it just reverts to an iterator or some 
> other alternative approach, or just gives up until next time.

This is absolutely wrong, erroneous approach. The behavior of Map when
keys change in respect to equals(), hashCode() or compareTo() is
undefined and the application should never allow it to happen. For
HashMap, you will end up with a stale entry which cannot be located
using either the original or the new key, yet somehow it will be
reported by containsValue() -- a clear inconsistency. For TreeMap, the
result is even more severe, since other mappings become affected -- you
will mess up a whole portion of the map just by messing up a single key.

> I prefer not to have the language/API preclude me from making 
> my own decisions about when to use the Atomic* classes and 
> when to use a
> hashCode() value which in the general case is not guaranteed 
> to be constant.  I am not meaning to say there is no 
> clean/simple/efficient way (or BETTER way) than the above to 
> get the same end result for the particular application, maybe 
> having a single official state value snapshot, or a single 
> update thread.  I just think using the Atomic* is a simple, 
> natural, reasonable way to do it, and I have not seen 
> anything to make me think it should not be a supported way to do it.

I claim that there is not a single legitimate use of value-based
comparison methods for AtomicX classes, and this claim so far has not
been proven wrong. Even if there was (but there isn't), it would be much
less common than default, identity-based comparison. Why making things
harder in cases which actually may be useful? And even if there was no
use of identity-based comparison either, the danger of misleading people
into using atomics as keys in maps (and severely hurt themselves as a
result) is enough to me to object supporting it.