Skip to content

Commit 9c08885

Browse files
authored
Multi-Release Virtual Thread Methods (#470)
- Avoid reflection when checking if the thread is virtual - use `threadId` over the deprecated `getId`
1 parent d93ed1c commit 9c08885

File tree

4 files changed

+72
-47
lines changed

4 files changed

+72
-47
lines changed

json-core/pom.xml

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,42 @@
3636
<version>1.7</version>
3737
<scope>test</scope>
3838
</dependency>
39-
4039
</dependencies>
40+
41+
<build>
42+
<plugins>
43+
<plugin>
44+
<groupId>org.apache.maven.plugins</groupId>
45+
<artifactId>maven-compiler-plugin</artifactId>
46+
<executions>
47+
<execution>
48+
<id>compile-java-21</id>
49+
<phase>compile</phase>
50+
<goals>
51+
<goal>compile</goal>
52+
</goals>
53+
<configuration>
54+
<release>21</release>
55+
<compileSourceRoots>
56+
<compileSourceRoot>
57+
${project.basedir}/src/main/java21</compileSourceRoot>
58+
</compileSourceRoots>
59+
<multiReleaseOutput>true</multiReleaseOutput>
60+
</configuration>
61+
</execution>
62+
</executions>
63+
</plugin>
64+
<plugin>
65+
<groupId>org.apache.maven.plugins</groupId>
66+
<artifactId>maven-jar-plugin</artifactId>
67+
<configuration>
68+
<archive>
69+
<manifestEntries>
70+
<Multi-Release>true</Multi-Release>
71+
</manifestEntries>
72+
</archive>
73+
</configuration>
74+
</plugin>
75+
</plugins>
76+
</build>
4177
</project>

json-core/src/main/java/io/avaje/json/stream/core/HybridBufferRecycler.java

Lines changed: 9 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55

66
import java.io.InputStream;
77
import java.lang.invoke.MethodHandle;
8-
import java.lang.invoke.MethodHandles;
9-
import java.lang.invoke.MethodType;
108
import java.util.concurrent.atomic.AtomicReferenceArray;
11-
import java.util.function.Predicate;
129

1310
/**
1411
* 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 {
3734

3835
private static final HybridBufferRecycler INSTANCE = new HybridBufferRecycler();
3936

40-
private static final Predicate<Thread> isVirtual = VirtualPredicate.findIsVirtualPredicate();
41-
4237
private static final BufferRecycler NATIVE_RECYCLER = ThreadLocalPool.shared();
4338
private static final BufferRecycler VIRTUAL_RECYCLER = StripedLockFreePool.shared();
39+
private static final boolean VT_ENABLED = Runtime.version().feature() >= 21;
4440

4541
private HybridBufferRecycler() {
4642
}
@@ -51,21 +47,21 @@ static HybridBufferRecycler shared() {
5147

5248
@Override
5349
public JsonGenerator generator(JsonOutput target) {
54-
return isVirtual.test(Thread.currentThread())
50+
return VT_ENABLED && ThreadFunctions.isVirtual()
5551
? VIRTUAL_RECYCLER.generator(target)
5652
: NATIVE_RECYCLER.generator(target);
5753
}
5854

5955
@Override
6056
public JsonParser parser(byte[] bytes) {
61-
return isVirtual.test(Thread.currentThread())
57+
return VT_ENABLED && ThreadFunctions.isVirtual()
6258
? VIRTUAL_RECYCLER.parser(bytes)
6359
: NATIVE_RECYCLER.parser(bytes);
6460
}
6561

6662
@Override
6763
public JsonParser parser(InputStream in) {
68-
return isVirtual.test(Thread.currentThread())
64+
return VT_ENABLED && ThreadFunctions.isVirtual()
6965
? VIRTUAL_RECYCLER.parser(in)
7066
: NATIVE_RECYCLER.parser(in);
7167
}
@@ -131,9 +127,8 @@ public JsonGenerator generator(JsonOutput target) {
131127
if (generatorStacks.compareAndSet(index, currentHead, currentHead.next)) {
132128
currentHead.next = null;
133129
return currentHead.value.prepare(target);
134-
} else {
135-
currentHead = generatorStacks.get(index);
136130
}
131+
currentHead = generatorStacks.get(index);
137132
}
138133
}
139134

@@ -149,9 +144,8 @@ private JsonParser parser() {
149144
if (parserStacks.compareAndSet(index, currentHead, currentHead.next)) {
150145
currentHead.next = null;
151146
return currentHead.value;
152-
} else {
153-
currentHead = parserStacks.get(index);
154147
}
148+
currentHead = parserStacks.get(index);
155149
}
156150
}
157151

@@ -166,9 +160,8 @@ public void recycle(JsonGenerator recycler) {
166160
if (generatorStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) {
167161
newHead.next = next;
168162
return;
169-
} else {
170-
next = generatorStacks.get(vThreadBufferRecycler.slot);
171163
}
164+
next = generatorStacks.get(vThreadBufferRecycler.slot);
172165
}
173166
}
174167

@@ -183,9 +176,8 @@ public void recycle(JsonParser recycler) {
183176
if (parserStacks.compareAndSet(vThreadBufferRecycler.slot, next, newHead)) {
184177
newHead.next = next;
185178
return;
186-
} else {
187-
next = parserStacks.get(vThreadBufferRecycler.slot);
188179
}
180+
next = parserStacks.get(vThreadBufferRecycler.slot);
189181
}
190182
}
191183

@@ -236,35 +228,6 @@ private VThreadJParser(int slot) {
236228
}
237229
}
238230

239-
private static final class VirtualPredicate {
240-
private static final MethodHandle virtualMh = findVirtualMH();
241-
242-
private static MethodHandle findVirtualMH() {
243-
try {
244-
return MethodHandles.publicLookup()
245-
.findVirtual(Thread.class, "isVirtual", MethodType.methodType(boolean.class));
246-
} catch (Exception e) {
247-
return null;
248-
}
249-
}
250-
251-
private static Predicate<Thread> findIsVirtualPredicate() {
252-
return virtualMh == null ? VirtualPredicate::notVirtual : VirtualPredicate::isVirtual;
253-
}
254-
255-
private static boolean isVirtual(Thread thread) {
256-
try {
257-
return (boolean) virtualMh.invokeExact(thread);
258-
} catch (Throwable e) {
259-
throw new RuntimeException(e);
260-
}
261-
}
262-
263-
private static boolean notVirtual(Thread thread) {
264-
return false;
265-
}
266-
}
267-
268231
private static final class XorShiftThreadProbe {
269232

270233
private final int mask;
@@ -282,7 +245,7 @@ private int probe() {
282245
// 0x9e3779b9 is the integral part of the Golden Ratio's fractional part 0.61803398875…
283246
// (sqrt(5)-1)/2
284247
// multiplied by 2^32, which has the best possible scattering properties.
285-
int probe = (int) ((Thread.currentThread().getId() * 0x9e3779b9) & Integer.MAX_VALUE);
248+
int probe = (int) ((ThreadFunctions.getId() * 0x9e3779b9) & Integer.MAX_VALUE);
286249
// xorshift
287250
probe ^= probe << 13;
288251
probe ^= probe >>> 17;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.avaje.json.stream.core;
2+
3+
final class ThreadFunctions {
4+
private ThreadFunctions() {}
5+
6+
static long getId() {
7+
return Thread.currentThread().getId();
8+
}
9+
10+
static boolean isVirtual() {
11+
return false;
12+
}
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.avaje.json.stream.core;
2+
3+
final class ThreadFunctions {
4+
private ThreadFunctions() {}
5+
6+
static long getId() {
7+
return Thread.currentThread().threadId();
8+
}
9+
10+
static boolean isVirtual() {
11+
return Thread.currentThread().isVirtual();
12+
}
13+
}

0 commit comments

Comments
 (0)