Add lazy-constants module: LazyConstant (JEP 531) against the holder idiom and double-checked locking

Basics, failure semantics on 26 vs 27, lazy collections, API across 25/26/27, preview-flag traps and a JMH read-path benchmark (jars fetched and sha1-checked, not committed).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
This commit is contained in:
Claude
2026-09-24 12:27:27 +00:00
parent af44e59e7f
commit 814c32b5ce
25 changed files with 606 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
lib/
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Downloads JMH 1.37 and its two runtime dependencies from Maven Central into ./lib, checking each against the published .sha1
set -euo pipefail
cd "$(dirname "$0")"; mkdir -p lib
M=https://repo1.maven.org/maven2
for a in org/openjdk/jmh/jmh-core/1.37/jmh-core-1.37 \
org/openjdk/jmh/jmh-generator-annprocess/1.37/jmh-generator-annprocess-1.37 \
net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4 \
org/apache/commons/commons-math3/3.6.1/commons-math3-3.6.1; do
f="lib/$(basename "$a").jar"
for try in 1 2 3 4 5; do [ -s "$f" ] && break; curl -sfL -o "$f" "$M/$a.jar" || { rm -f "$f"; sleep 5; }; done
want=""; for try in 1 2 3 4 5; do want="$(curl -sfL "$M/$a.jar.sha1" | cut -d' ' -f1)" && [ -n "$want" ] && break; sleep 5; done
got="$(sha1sum "$f" | cut -d' ' -f1)"
[ "$want" = "$got" ] && echo "ok $f $got" || { echo "SHA1 MISMATCH $f"; exit 1; }
done
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Builds and runs the JMH read-path benchmark on JDK 27 and writes ../output/07-jmh.txt (about 3 minutes).
# JDK27=/path ./run-jmh.sh (run ./fetch.sh once first)
set -uo pipefail
unset JAVA_TOOL_OPTIONS
HERE="$(cd "$(dirname "$0")" && pwd)"; cd "$HERE"
JDK27="${JDK27:-/opt/jdks/jdk27}"
CP="$(ls lib/*.jar | tr '\n' ':')"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT
"$JDK27/bin/javac" --enable-preview --release 27 -cp "$CP" -processorpath "$CP" -d "$B" src/bench/AccessBench.java 2>&1 | grep -v '^Note:'
{
echo "\$ java --enable-preview org.openjdk.jmh.Main -f 3 -wi 3 -w 1 -i 5 -r 1 ($("$JDK27/bin/java" -version 2>&1 | sed -n 2p | sed 's/.*(build \(.*\))/\1/'), JMH 1.37)"
echo "machine: $(nproc) CPUs, $(grep -m1 'model name' /proc/cpuinfo | sed 's/.*: //')"
"$JDK27/bin/java" --enable-preview -cp "$B:$CP" org.openjdk.jmh.Main -f 3 -wi 3 -w 1 -i 5 -r 1 -jvmArgs "--enable-preview" 2>&1 | sed -n '/^Benchmark /,$p'
} > "$HERE/../output/07-jmh.txt"
cat "$HERE/../output/07-jmh.txt"
@@ -0,0 +1,52 @@
package bench;
import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;
/**
* Cost of READING an already-initialised lazily-built value, six ways. Every variant returns k + x, where x is an
* ordinary mutable field, so the JIT cannot fold the whole method away, but can fold a constant k.
*/
@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class AccessBench {
record Config(int k) {}
// 1. Eager static final: the reference point, always initialised, always foldable.
static final Config EAGER = new Config(7);
// 2. Holder idiom.
static class Holder { static final Config VALUE = new Config(7); }
// 3. Double-checked locking with a volatile field.
static volatile Config dclField;
static Config dcl() {
Config local = dclField;
if (local == null) {
synchronized (AccessBench.class) {
local = dclField;
if (local == null) dclField = local = new Config(7);
}
}
return local;
}
// 4. LazyConstant in a static final field: the case the JEP is designed for.
static final LazyConstant<Config> LAZY_STATIC = LazyConstant.of(() -> new Config(7));
// 5. LazyConstant in a static field that is NOT final.
static LazyConstant<Config> lazyNonFinalStatic = LazyConstant.of(() -> new Config(7));
// 6. LazyConstant in an ordinary final instance field.
final LazyConstant<Config> lazyInstance = LazyConstant.of(() -> new Config(7));
int x = 3;
@Benchmark public int eagerStaticFinal() { return EAGER.k() + x; }
@Benchmark public int holderIdiom() { return Holder.VALUE.k() + x; }
@Benchmark public int doubleCheckedLocking() { return dcl().k() + x; }
@Benchmark public int lazyConstantStaticFinal() { return LAZY_STATIC.get().k() + x; }
@Benchmark public int lazyConstantNonFinalStatic() { return lazyNonFinalStatic.get().k() + x; }
@Benchmark public int lazyConstantInstanceField() { return lazyInstance.get().k() + x; }
}