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

Charles Oliver Nutter headius at headius.com
Wed May 21 20:53:52 UTC 2025


Ok thanks for the confirmations!

If I'm understanding correctly, in the future the way to guarantee
once-only initialization of a ClassValue would be to have it produce a
StableValue, of which only one will be installed, and which will then be
guaranteed by the JVM to have once-only initialization and a foldable,
stable result. Until that is generally available, I should use a factory
object with double-checked locking to ensure once-only initialization
(which is essentially what my own ClassValue did, but built as a wrapper
around java.lang.ClassValue).

On Wed, May 21, 2025 at 1:59 PM Remi Forax <forax at univ-mlv.fr> wrote:

>
>
> ------------------------------
>
> *From: *"Chen Liang" <chen.l.liang at oracle.com>
> *To: *"Da Vinci Machine Project" <mlvm-dev at openjdk.org>, "Charles Oliver
> Nutter" <headius at headius.com>
> *Sent: *Wednesday, May 21, 2025 8:47:24 PM
> *Subject: *Re: Years later, finally integrating ClassValue... but...
>
> 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.
>
>
> yes, a double check locking (or a stable value but it's in preview in 25)
> solve the issue.
>
>
> Regards,
> Chen Liang
>
>
> Rémi
>
> ------------------------------
> *From:* mlvm-dev <mlvm-dev-retn at openjdk.org> on behalf of Charles Oliver
> Nutter <headius at headius.com>
> *Sent:* Wednesday, May 21, 2025 12:42 PM
> *To:* Da Vinci Machine Project <mlvm-dev at openjdk.java.net>
> *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
> headius at headius.com
>
> _______________________________________________
> mlvm-dev mailing list
> mlvm-dev at openjdk.org
> https://mail.openjdk.org/mailman/listinfo/mlvm-dev
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/mlvm-dev/attachments/20250521/c3ad0f7a/attachment.htm>


More information about the mlvm-dev mailing list