diff --git a/src/java.base/share/classes/java/lang/StableValue.java b/src/java.base/share/classes/java/lang/StableValue.java new file mode 100644 index 0000000000000..37ffd686310ef --- /dev/null +++ b/src/java.base/share/classes/java/lang/StableValue.java @@ -0,0 +1,498 @@ +/* + * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package java.lang; + +import jdk.internal.access.SharedSecrets; +import jdk.internal.javac.PreviewFeature; +import jdk.internal.lang.stable.StableValueImpl; +import jdk.internal.lang.stable.StableValueUtil; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ThreadFactory; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; + +/** + * A thin, atomic, non-blocking, thread-safe, set-at-most-once, stable value holder + * eligible for certain JVM optimizations if set to a value. + *

+ * A stable value is said to be monotonic because the state of a stable value can only go + * from unset to set and consequently, a value can only be set + * at most once. + *

+ * StableValue is mainly intended to be a member of a holding class and is usually neither + * exposed directly via accessors nor passed as a method parameter. + * + *

Factories

+ *

+ * To create a new fresh (unset) StableValue, use the + * {@linkplain StableValue#newInstance() StableValue::newInstance} factory. + *

+ * This class also contains a number of convenience methods for creating constructs + * involving stable values: + *

+ *

+ * The constructs above are eligible for similar JVM optimizations as StableValue + * instances. + * + *

Memory Consistency Properties

+ * Actions on a presumptive holder value in a thread prior to calling a method that sets + * the holder value are seen by any other thread that observes a set holder value. + * + * More generally, the action of attempting to interact (i.e. via load or store operations) + * with a StableValue's holder value (e.g. via {@link StableValue#trySet} or + * {@link StableValue#orElseThrow()}) forms a + * happens-before + * relation between any other attempt to interact with the StableValue's holder value. + * + *

Nullability

+ * Except for a StableValue's holder value itself, all method parameters must be + * non-null or a {@link NullPointerException} will be thrown. + * + * + * Implementations of this interface can be + * value-based + * classes; programmers should treat instances that are + * {@linkplain Object#equals(Object) equal} as interchangeable and should not + * use instances for synchronization, or unpredictable behavior may + * occur. For example, in a future release, synchronization may fail. + * The {@code equals} method should be used for comparisons. + * + * @param type of the holder value + * + * @since 24 + */ +@PreviewFeature(feature = PreviewFeature.Feature.STABLE_VALUES) +public sealed interface StableValue + permits StableValueImpl { + + // Principal methods + + /** + * {@return {@code true} if the holder value was set to the provided {@code value}, + * otherwise returns {@code false}} + *

+ * When this method returns, a holder value is always set. + * + * @param value to set (nullable) + */ + boolean trySet(T value); + + /** + * {@return the set holder value (nullable) if set, otherwise return the + * {@code other} value} + * + * @param other to return if the stable holder value is not set + */ + T orElse(T other); + + /** + * {@return the set holder value if set, otherwise throws {@code NoSuchElementException}} + * + * @throws NoSuchElementException if no value is set + */ + T orElseThrow(); + + /** + * {@return {@code true} if a holder value is set, {@code false} otherwise} + */ + boolean isSet(); + + // Convenience methods + + /** + * Sets the holder value to the provided {@code value}, or, if already set, + * throws {@link IllegalStateException}} + *

+ * When this method returns (or throws an Exception), a holder value is always set. + * + * @param value to set (nullable) + * @throws IllegalStateException if a holder value is already set + */ + default void setOrThrow(T value) { + if (!trySet(value)) { + throw new IllegalStateException("Cannot set the holder value to " + value + + " because a holder value is alredy set: " + this); + } + } + + // Factories + + /** + * {@return a fresh stable value with an unset holder value} + * + * @param type of the holder value + */ + static StableValue newInstance() { + return StableValueUtil.newInstance(); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@linkplain Supplier supplier} that records the value of the provided + * {@code original} supplier upon being first accessed via + * {@linkplain Supplier#get() Supplier::get}} + *

+ * The provided {@code original} supplier is guaranteed to be successfully invoked + * at most once even in a multi-threaded environment. Competing threads invoking the + * {@linkplain Supplier#get() Supplier::get} method when a value is already under + * computation will block until a value is computed or an exception is thrown by the + * computing thread. + *

+ * If the {@code original} Supplier invokes the returned Supplier recursively, + * a StackOverflowError will be thrown when the returned + * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. + *

+ * If the provided {@code original} supplier throws an exception, it is relayed + * to the initial caller. + * + * @param original supplier used to compute a memoized value + * @param the type of results supplied by the returned supplier + */ + static Supplier newCachingSupplier(Supplier original) { + Objects.requireNonNull(original); + return StableValueUtil.newCachingSupplier(original, null); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@linkplain Supplier supplier} that records the value of the provided + * {@code original} supplier upon being first accessed via + * {@linkplain Supplier#get() Supplier::get}, or via a background thread + * created from the provided {@code factory}} + *

+ * The provided {@code original} supplier is guaranteed to be successfully invoked + * at most once even in a multi-threaded environment. Competing threads invoking the + * {@linkplain Supplier#get() Supplier::get} method when a value is already under + * computation will block until a value is computed or an exception is thrown by the + * computing thread. + *

+ * If the {@code original} Supplier invokes the returned Supplier recursively, + * a StackOverflowError will be thrown when the returned + * Supplier's {@linkplain Supplier#get() Supplier::get} method is invoked. + *

+ * If the provided {@code original} supplier throws an exception, it is relayed + * to the initial caller. If the memoized supplier is computed by a background thread, + * exceptions from the provided {@code original} supplier will be relayed to the + * background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. + * + * @param original supplier used to compute a memoized value + * @param factory a factory that will be used to create a background thread that will + * attempt to compute the memoized value. + * @param the type of results supplied by the returned supplier + */ + static Supplier newCachingSupplier(Supplier original, + ThreadFactory factory) { + Objects.requireNonNull(original); + Objects.requireNonNull(factory); + return StableValueUtil.newCachingSupplier(original, factory); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@link IntFunction } that, for each allowed input, records the values of the + * provided {@code original} IntFunction upon being first accessed via + * {@linkplain IntFunction#apply(int) IntFunction::apply}} + *

+ * The provided {@code original} IntFunction is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain IntFunction#apply(int) IntFunction::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. + *

+ * If the {@code original} IntFunction invokes the returned IntFunction recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} method is + * invoked. + *

+ * If the provided {@code original} IntFunction throws an exception, it is relayed + * to the initial caller. + * + * @param size the size of the allowed inputs in {@code [0, size)} + * @param original IntFunction used to compute a memoized value + * @param the type of results delivered by the returned IntFunction + */ + static IntFunction newCachingIntFunction(int size, + IntFunction original) { + if (size < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(original); + return StableValueUtil.newCachingIntFunction(size, original, null); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed + * {@link IntFunction } that, for each allowed input, records the values of the + * provided {@code original} IntFunction upon being first accessed via + * {@linkplain IntFunction#apply(int) IntFunction::apply}, or via background + * threads created from the provided {@code factory} (if non-null)} + *

+ * The provided {@code original} IntFunction is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain IntFunction#apply(int) IntFunction::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. + *

+ * If the {@code original} IntFunction invokes the returned IntFunction recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * IntFunction's {@linkplain IntFunction#apply(int) IntFunction::apply} method is + * invoked. + *

+ * If the provided {@code original} IntFunction throws an exception, it is relayed + * to the initial caller. If the memoized IntFunction is computed by a background + * thread, exceptions from the provided {@code original} IntFunction will be relayed + * to the background thread's {@linkplain Thread#getUncaughtExceptionHandler() + * uncaught exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param size the size of the allowed inputs in {@code [0, size)} + * @param original IntFunction used to compute a memoized value + * @param factory a factory that will be used to create {@code size} background + * threads that will attempt to compute all the memoized values. + * @param the type of results delivered by the returned IntFunction + */ + static IntFunction newCachingIntFunction(int size, + IntFunction original, + ThreadFactory factory) { + if (size < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(original); + Objects.requireNonNull(factory); + return StableValueUtil.newCachingIntFunction(size, original, factory); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed {@link Function} + * that, for each allowed input in the given set of {@code inputs}, records the + * values of the provided {@code original} Function upon being first accessed via + * {@linkplain Function#apply(Object) Function::apply}, or optionally via background + * threads created from the provided {@code factory} (if non-null)} + *

+ * The provided {@code original} Function is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain Function#apply(Object) Function::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. + *

+ * If the {@code original} Function invokes the returned Function recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * Function's {@linkplain Function#apply(Object) Function::apply} method is invoked. + *

+ * If the provided {@code original} Function throws an exception, it is relayed + * to the initial caller. If the memoized Function is computed by a background + * thread, exceptions from the provided {@code original} Function will be relayed to + * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param inputs the set of allowed input values + * @param original Function used to compute a memoized value + * @param the type of the input to the returned Function + * @param the type of results delivered by the returned Function + */ + static Function newCachingFunction(Set inputs, + Function original) { + Objects.requireNonNull(inputs); + Objects.requireNonNull(original); + return StableValueUtil.newCachingFunction(inputs, original, null); + } + + /** + * {@return a new caching, thread-safe, stable, lazily computed {@link Function} + * that, for each allowed input in the given set of {@code inputs}, records the + * values of the provided {@code original} Function upon being first accessed via + * {@linkplain Function#apply(Object) Function::apply}, or via background + * threads created from the provided {@code factory} (if non-null)} + *

+ * The provided {@code original} Function is guaranteed to be successfully invoked + * at most once per allowed input, even in a multi-threaded environment. Competing + * threads invoking the {@linkplain Function#apply(Object) Function::apply} method + * when a value is already under computation will block until a value is computed or + * an exception is thrown by the computing thread. + *

+ * If the {@code original} Function invokes the returned Function recursively + * for a particular input value, a StackOverflowError will be thrown when the returned + * Function's {@linkplain Function#apply(Object) Function::apply} method is invoked. + *

+ * If the provided {@code original} Function throws an exception, it is relayed + * to the initial caller. If the memoized Function is computed by a background + * thread, exceptions from the provided {@code original} Function will be relayed to + * the background thread's {@linkplain Thread#getUncaughtExceptionHandler() uncaught + * exception handler}. + *

+ * The order in which background threads are started is unspecified. + * + * @param inputs the set of allowed input values + * @param original Function used to compute a memoized value + * @param factory a factory that will be used to create {@code size} background + * threads that will attempt to compute the memoized values. + * @param the type of the input to the returned Function + * @param the type of results delivered by the returned Function + */ + static Function newCachingFunction(Set inputs, + Function original, + ThreadFactory factory) { + Objects.requireNonNull(inputs); + Objects.requireNonNull(original); + Objects.requireNonNull(factory); + return StableValueUtil.newCachingFunction(inputs, original, factory); + } + + /** + * {@return a lazy, shallowly immutable, stable List of the provided {@code size} + * where the individual elements of the list are lazily computed via the provided + * {@code mapper} whenever an element is first accessed (directly or indirectly), + * for example via {@linkplain List#get(int) List::get}} + *

+ * The provided {@code mapper} IntFunction is guaranteed to be successfully invoked + * at most once per list index, even in a multi-threaded environment. Competing + * threads accessing an element already under computation will block until an element + * is computed or an exception is thrown by the computing thread. + *

+ * If the {@code mapper} IntFunction invokes the returned IntFunction recursively + * for a particular index, a StackOverflowError will be thrown when the returned + * List's {@linkplain List#get(int) List::get} method is invoked. + *

+ * If the provided {@code mapper} IntFunction throws an exception, it is relayed + * to the initial caller and no element is computed. + *

+ * The returned List is not {@link Serializable} + * + * @param size the size of the returned list + * @param mapper to invoke whenever an element is first accessed (may return null) + * @param the {@code StableValue}s' element type + */ + static List lazyList(int size, IntFunction mapper) { + if (size < 0) { + throw new IllegalArgumentException(); + } + Objects.requireNonNull(mapper); + return SharedSecrets.getJavaUtilCollectionAccess().lazyList(size, mapper); + } + + /** + * {@return a lazy, shallowly immutable, stable Map of the provided {@code keys} + * where the associated values of the maps are lazily computed vio the provided + * {@code mapper} whenever a value is first accessed (directly or indirectly), for + * example via {@linkplain Map#get(Object) Map::get}} + *

+ * The provided {@code mapper} Function is guaranteed to be successfully invoked + * at most once per key, even in a multi-threaded environment. Competing + * threads accessing an associated value already under computation will block until + * an associated value is computed or an exception is thrown by the computing thread. + *

+ * If the {@code mapper} Function invokes the returned Map recursively + * for a particular key, a StackOverflowError will be thrown when the returned + * Map's {@linkplain Map#get(Object) Map::get}} method is invoked. + *

+ * If the provided {@code mapper} Function throws an exception, it is relayed + * to the initial caller and no value is computed. + *

+ * The returned Map is not {@link Serializable} + * + * @param keys the keys in the returned map + * @param mapper to invoke whenever an associated value is first accessed + * (may return null) + * @param the type of keys maintained by the returned map + * @param the type of mapped values + */ + static Map lazyMap(Set keys, Function mapper) { + Objects.requireNonNull(keys); + Objects.requireNonNull(mapper); + return SharedSecrets.getJavaUtilCollectionAccess().lazyMap(keys, mapper); + } + +}