Years later, finally integrating ClassValue... but...

Charles Oliver Nutter headius at headius.com
Wed May 21 17:42:40 UTC 2025


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
headius at headius.com
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/mlvm-dev/attachments/20250521/a921b268/attachment-0001.htm>


More information about the mlvm-dev mailing list