From b26d0e8b5ede9fd41eafe867275507da79fb0fed Mon Sep 17 00:00:00 2001 From: Josiah Noel <32279667+SentryMan@users.noreply.github.com> Date: Wed, 3 Dec 2025 22:46:56 -0500 Subject: [PATCH 1/2] Multi-Release Virtual Thread Methods - Avoid reflection when checking if the thread is virtual - use `threadId` over the deprecated `getId` --- json-core/pom.xml | 38 ++++++++++++- .../stream/core/HybridBufferRecycler.java | 55 +++---------------- .../json/stream/core/ThreadFunctions.java | 13 +++++ .../json/stream/core/ThreadFunctions.java | 13 +++++ 4 files changed, 72 insertions(+), 47 deletions(-) create mode 100644 json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java create mode 100644 json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java diff --git a/json-core/pom.xml b/json-core/pom.xml index cb5709e1..03b843e4 100644 --- a/json-core/pom.xml +++ b/json-core/pom.xml @@ -36,6 +36,42 @@ 1.7 test - + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + compile-java-21 + compile + + compile + + + 21 + + + ${project.basedir}/src/main/java21 + + true + + + + + + org.apache.maven.plugins + maven-jar-plugin + + + + true + + + + + + diff --git a/json-core/src/main/java/io/avaje/json/stream/core/HybridBufferRecycler.java b/json-core/src/main/java/io/avaje/json/stream/core/HybridBufferRecycler.java index 394b060b..f0bf46b6 100644 --- a/json-core/src/main/java/io/avaje/json/stream/core/HybridBufferRecycler.java +++ b/json-core/src/main/java/io/avaje/json/stream/core/HybridBufferRecycler.java @@ -5,10 +5,7 @@ import java.io.InputStream; import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; import java.util.concurrent.atomic.AtomicReferenceArray; -import java.util.function.Predicate; /** * This is a custom implementation of the Jackson's RecyclerPool intended to work equally well with @@ -37,10 +34,9 @@ final class HybridBufferRecycler implements BufferRecycler { private static final HybridBufferRecycler INSTANCE = new HybridBufferRecycler(); - private static final Predicate isVirtual = VirtualPredicate.findIsVirtualPredicate(); - private static final BufferRecycler NATIVE_RECYCLER = ThreadLocalPool.shared(); private static final BufferRecycler VIRTUAL_RECYCLER = StripedLockFreePool.shared(); + private static final boolean VT_ENABLED = Runtime.version().feature() >= 21; private HybridBufferRecycler() { } @@ -51,21 +47,21 @@ static HybridBufferRecycler shared() { @Override public JsonGenerator generator(JsonOutput target) { - return isVirtual.test(Thread.currentThread()) + return VT_ENABLED && ThreadFunctions.isVirtual() ? VIRTUAL_RECYCLER.generator(target) : NATIVE_RECYCLER.generator(target); } @Override public JsonParser parser(byte[] bytes) { - return isVirtual.test(Thread.currentThread()) + return VT_ENABLED && ThreadFunctions.isVirtual() ? VIRTUAL_RECYCLER.parser(bytes) : NATIVE_RECYCLER.parser(bytes); } @Override public JsonParser parser(InputStream in) { - return isVirtual.test(Thread.currentThread()) + return VT_ENABLED && ThreadFunctions.isVirtual() ? VIRTUAL_RECYCLER.parser(in) : NATIVE_RECYCLER.parser(in); } @@ -131,9 +127,8 @@ public JsonGenerator generator(JsonOutput target) { if (generatorStacks.compareAndSet(index, currentHead, currentHead.next)) { currentHead.next = null; return currentHead.value.prepare(target); - } else { - currentHead = generatorStacks.get(index); } + currentHead = generatorStacks.get(index); } } @@ -149,9 +144,8 @@ private JsonParser parser() { if (parserStacks.compareAndSet(index, currentHead, currentHead.next)) { currentHead.next = null; return currentHead.value; - } else { - currentHead = parserStacks.get(index); } + currentHead = parserStacks.get(index); } } @@ -166,9 +160,8 @@ public void recycle(JsonGenerator recycler) { if (generatorStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) { newHead.next = next; return; - } else { - next = generatorStacks.get(vThreadBufferRecycler.slot); } + next = generatorStacks.get(vThreadBufferRecycler.slot); } } @@ -183,9 +176,8 @@ public void recycle(JsonParser recycler) { if (parserStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) { newHead.next = next; return; - } else { - next = parserStacks.get(vThreadBufferRecycler.slot); } + next = parserStacks.get(vThreadBufferRecycler.slot); } } @@ -236,35 +228,6 @@ private VThreadJParser(int slot) { } } - private static final class VirtualPredicate { - private static final MethodHandle virtualMh = findVirtualMH(); - - private static MethodHandle findVirtualMH() { - try { - return MethodHandles.publicLookup() - .findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class)); - } catch (Exception e) { - return null; - } - } - - private static Predicate findIsVirtualPredicate() { - return virtualMh == null ? VirtualPredicate::notVirtual : VirtualPredicate::isVirtual; - } - - private static boolean isVirtual(Thread thread) { - try { - return (boolean) virtualMh.invokeExact(thread); - } catch (Throwable e) { - throw new RuntimeException(e); - } - } - - private static boolean notVirtual(Thread thread) { - return false; - } - } - private static final class XorShiftThreadProbe { private final int mask; @@ -282,7 +245,7 @@ private int probe() { // 0x9e3779b9 is the integral part of the Golden Ratio's fractional part 0.61803398875… // (sqrt(5)-1)/2 // multiplied by 2^32, which has the best possible scattering properties. - int probe = (int) ((Thread.currentThread().getId() * 0x9e3779b9) & Integer.MAX_VALUE); + int probe = (int) ((ThreadFunctions.getId() * 0x9e3779b9) & Integer.MAX_VALUE); // xorshift probe ^= probe << 13; probe ^= probe >>> 17; diff --git a/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java b/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java new file mode 100644 index 00000000..94f20fa9 --- /dev/null +++ b/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java @@ -0,0 +1,13 @@ +package io.avaje.json.stream.core; + +final class ThreadFunctions { + private ThreadFunctions() {} + + public static long getId() { + return Thread.currentThread().getId(); + } + + public static boolean isVirtual() { + return false; + } +} diff --git a/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java b/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java new file mode 100644 index 00000000..14375e14 --- /dev/null +++ b/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java @@ -0,0 +1,13 @@ +package io.avaje.json.stream.core; + +final class ThreadFunctions { + private ThreadFunctions() {} + + public static long getId() { + return Thread.currentThread().threadId(); + } + + public static boolean isVirtual() { + return Thread.currentThread().isVirtual(); + } +} From bbe1ed2706541dfe70ad80294b6e0c6416f7f8ce Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 4 Dec 2025 21:38:29 +1300 Subject: [PATCH 2/2] ThreadFunctions methods can be package private --- .../main/java/io/avaje/json/stream/core/ThreadFunctions.java | 4 ++-- .../java21/io/avaje/json/stream/core/ThreadFunctions.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java b/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java index 94f20fa9..71f82452 100644 --- a/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java +++ b/json-core/src/main/java/io/avaje/json/stream/core/ThreadFunctions.java @@ -3,11 +3,11 @@ final class ThreadFunctions { private ThreadFunctions() {} - public static long getId() { + static long getId() { return Thread.currentThread().getId(); } - public static boolean isVirtual() { + static boolean isVirtual() { return false; } } diff --git a/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java b/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java index 14375e14..6048483c 100644 --- a/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java +++ b/json-core/src/main/java21/io/avaje/json/stream/core/ThreadFunctions.java @@ -3,11 +3,11 @@ final class ThreadFunctions { private ThreadFunctions() {} - public static long getId() { + static long getId() { return Thread.currentThread().threadId(); } - public static boolean isVirtual() { + static boolean isVirtual() { return Thread.currentThread().isVirtual(); } }