Hi Charles,
Indeed, the docs for "computeValue" was wrong. The doc for "get" was right. The 
upcoming JDK 25 will provide a spec update that simplifies the mental model of 
a ClassValue and correct these mistakes in the docs.

ClassValue always have been utilizing racy computation, that multiple threads 
can compute a value, so there is minimal locking - the only locking would be at 
the time when a thread tries to associate a value. This association is 
essentially a CAS, where only one thread's computeValue result is ever 
installed. There is no synchronization lock on computeValue, and there is no 
way for a synchronized computeValue to check whether a value is already 
associated. The only threading guarantee provided by ClassValue is that the 
associated value's identity/value is unique and its computation result is 
published to all threads that saw the value via a get.

If you wish for synchronous initialization, I think you can store a container, 
such as a memoized function (such as StableValue::supplier), in a ClassValue, 
and then call the function (or a synchronized initializer) on all accessing 
threads - this ensures the is exactly one memoized function object, and that 
object is initialized exactly once.

Regards,
Chen Liang
________________________________
From: mlvm-dev <[email protected]> on behalf of Charles Oliver Nutter 
<[email protected]>
Sent: Wednesday, May 21, 2025 12:42 PM
To: Da Vinci Machine Project <[email protected]>
Subject: Years later, finally integrating ClassValue... but...

Due to issues for years trying to understand the lifecycle of class/value pairs 
stored in ClassValue, I am only now integrating the JDK-provided version of 
ClassValue into JRuby's logic to store method tables for Java classes. And I 
have run into a new inconsistency I want to clarify.

The docs for ClassValue state this for the "computeValue" method:

"This method will be invoked within the first thread that accesses the value 
with the get method.
Normally, this method is invoked at most once per class, but it may be invoked 
again if there has been a call to remove."

To me, that means computeValue will be invoked *exactly once* per class 
(ignoring removals), regardless of how many threads attempt to compute it at 
the same time. Otherwise, what's the point of saying it will be invoked by the 
"first thread"?

But then the docs for "get" say something different:

"Returns the value for the given class. If no value has yet been computed, it 
is obtained by an invocation of the computeValue method.
The actual installation of the value on the class is performed atomically. At 
that point, if several racing threads have computed values, one is chosen, and 
returned to all the racing threads."

Ok hold up... so now it's possible for multiple threads to independently 
computeValue? These two statements don't appear to mesh... I'm looking for the 
behavior computeValue describes: basically computeIfAbsent. But in practice 
(and from what I have read of the current implementation), multiple threads 
might call computeValue for a given class.

In JRuby, where the computation of this value also sets up global namespace 
tables, it results in warnings that the namespace entry has been initialized 
multiple times.

Adding synchronized to my implementation of computeValue does not help; it just 
means two threads don't compute at the same time. They will block until the 
first thread finishes its computeValue, and while that first thread is 
initializing the ClassValue, they'll proceed to computeValue several more times.

This was not a problem with my home-grown ClassValue implementation because I 
double-check the cache before proceeding into synchronized code that makes the 
computeValue call (computeIfAbsent behavior).

I can work around this by also overriding ClassValue.get to be synchronized, 
but the existing behavior does not seem right to me. It works this way on 1.8, 
21, and 24, so nothing has changed. Either the docs are wrong or the 
implementation is wrong.

Charles Oliver Nutter
Architect and Technologist
Headius Enterprises
https://www.headius.com
[email protected]<mailto:[email protected]>
_______________________________________________
mlvm-dev mailing list
[email protected]
https://mail.openjdk.org/mailman/listinfo/mlvm-dev

Reply via email to