Files

95 lines
4.6 KiB
Java

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* JEP 531, Lazy Constants (third preview), against the JDK 27 API.
*
* <p>A lazy constant is a value that is computed the first time somebody asks for it and never again,
* even when many threads ask at once. The JVM can then treat it like a {@code final} field. What changed
* since JDK 26: {@code orElse(...)} and {@code isInitialized()} are gone (see broken/Lazy26.java), and
* {@code Set.ofLazy(...)} arrived next to {@code List.ofLazy} and {@code Map.ofLazy}.
* Explained in docs/06-lazy-constants.md. Compile and run with --enable-preview.
*/
public class LazyDemo {
static final AtomicInteger INITIALISATIONS = new AtomicInteger();
/** The expensive thing: pretend this reads a file and parses it. */
record Settings(String region, int poolSize) {}
static Settings loadSettings() {
INITIALISATIONS.incrementAndGet();
System.out.println(" (loading settings on " + Thread.currentThread().getName() + ")");
return new Settings("ap-south-1", 16);
}
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
/** Identity hashes and lambda addresses change on every run; blank them so the transcript is stable. */
static String tidy(Object o) {
return o.toString().replaceAll("@[0-9a-f]+", "@...").replaceAll("/0x[0-9a-f]+", "/0x...");
}
public static void main(String[] args) throws Exception {
System.out.println("--- a lazy constant computes once, on first use");
System.out.println("toString before first get(): " + tidy(SETTINGS));
Settings first = SETTINGS.get();
Settings second = SETTINGS.get();
System.out.println("toString after first get(): " + tidy(SETTINGS));
Check.that(first == second, "get() returns the same instance every time");
Check.that(INITIALISATIONS.get() == 1, "the supplier ran exactly once");
System.out.println("--- and once even when 64 threads race for it");
var raced = LazyConstant.of(() -> {
INITIALISATIONS.incrementAndGet();
return "computed";
});
int before = INITIALISATIONS.get();
var start = new CountDownLatch(1);
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
for (int i = 0; i < 64; i++) {
pool.submit(() -> {
start.await();
return raced.get();
});
}
start.countDown();
}
Check.that(INITIALISATIONS.get() - before == 1, "64 racing threads triggered exactly one initialisation");
System.out.println("--- List.ofLazy: each element is computed on first access");
AtomicInteger listComputations = new AtomicInteger();
List<String> squares = List.ofLazy(5, i -> {
listComputations.incrementAndGet();
return "square(" + i + ")=" + (i * i);
});
System.out.println("computed so far: " + listComputations.get());
System.out.println("squares.get(3) = " + squares.get(3));
System.out.println("squares.get(3) = " + squares.get(3) + " (second read)");
Check.that(listComputations.get() == 1, "only element 3 was ever computed");
System.out.println("--- Map.ofLazy: the keys are fixed, the values are computed on first access");
AtomicInteger mapComputations = new AtomicInteger();
Map<String, Integer> lengths = Map.ofLazy(Set.of("alpha", "beta", "gamma"), k -> {
mapComputations.incrementAndGet();
return k.length();
});
System.out.println("lengths.get(\"gamma\") = " + lengths.get("gamma"));
Check.that(mapComputations.get() == 1, "only the requested key's value was computed");
System.out.println("--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand");
AtomicInteger setComputations = new AtomicInteger();
Set<Integer> primes = Set.ofLazy(Set.of(2, 3, 4, 5, 6, 7), n -> {
setComputations.incrementAndGet();
return n > 1 && java.util.stream.IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0);
});
System.out.println("primes.contains(7) = " + primes.contains(7) + ", primes.contains(6) = " + primes.contains(6));
Check.that(primes.contains(7) && !primes.contains(6), "the lazy set answers membership using the predicate");
System.out.println("primes = " + new java.util.TreeSet<>(primes));
}
}