diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 75599414..a4c4a3a5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -29,6 +29,7 @@ jobs:
java-version: |
17
21
+ 24
- name: Build with Maven
- run: mvn verify -ntp -B "-Djava17.home=${{env.JAVA_HOME_17_X64}}${{env.JAVA_HOME_17_ARM64}}"
+ run: mvn verify -ntp -B "-Djava17.home=${{env.JAVA_HOME_17_X64}}${{env.JAVA_HOME_17_ARM64}} -Djava21.home=${{env.JAVA_HOME_21_X64}}${{env.JAVA_HOME_21_ARM64}}"
diff --git a/pom.xml b/pom.xml
index 796f4239..f06568f7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -63,7 +63,7 @@
false
false
- 11
+ 24
3.0.3.Final
diff --git a/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java b/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java
index 3ce0b001..6da1bb65 100644
--- a/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java
+++ b/src/main/java/org/jboss/threads/EnhancedQueueExecutor.java
@@ -3,12 +3,15 @@
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.Thread.currentThread;
+import static java.lang.invoke.MethodHandles.lookup;
import static java.security.AccessController.doPrivileged;
import static java.security.AccessController.getContext;
import static java.util.concurrent.locks.LockSupport.parkNanos;
import static java.util.concurrent.locks.LockSupport.unpark;
-import static org.jboss.threads.JBossExecutors.unsafe;
+import java.lang.invoke.ConstantBootstraps;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
import java.lang.management.ManagementFactory;
import java.security.AccessControlContext;
import java.security.PrivilegedAction;
@@ -72,6 +75,15 @@
public final class EnhancedQueueExecutor extends AbstractExecutorService implements ManageableThreadPoolExecutorService, ScheduledExecutorService {
private static final Thread[] NO_THREADS = new Thread[0];
+ private static final VarHandle objArrayHandle = MethodHandles.arrayElementVarHandle(TaskNode[].class);
+ private static final VarHandle longArrayHandle = MethodHandles.arrayElementVarHandle(long[].class);
+
+ private static final VarHandle terminationWaitersHandle = ConstantBootstraps.fieldVarHandle(lookup(), "terminationWaiters", VarHandle.class, EnhancedQueueExecutor.class, Waiter.class);
+
+ private static final VarHandle peakThreadCountHandle = ConstantBootstraps.fieldVarHandle(lookup(), "peakThreadCount", VarHandle.class, EnhancedQueueExecutor.class, int.class);
+ private static final VarHandle activeCountHandle = ConstantBootstraps.fieldVarHandle(lookup(), "activeCount", VarHandle.class, EnhancedQueueExecutor.class, int.class);
+ private static final VarHandle peakQueueSizeHandle = ConstantBootstraps.fieldVarHandle(lookup(), "peakQueueSize", VarHandle.class, EnhancedQueueExecutor.class, int.class);
+
static {
Version.getVersionString();
MBeanUnregisterAction.forceInit();
@@ -316,12 +328,6 @@ public final class EnhancedQueueExecutor extends AbstractExecutorService impleme
private static final int numUnsharedLongs = 2;
private static final int numUnsharedObjects = 2;
- private static final long terminationWaitersOffset;
-
- private static final long peakThreadCountOffset;
- private static final long activeCountOffset;
- private static final long peakQueueSizeOffset;
-
// GraalVM should initialize this class at run time, which we instruct it to do in
// src/main/resources/META-INF/native-image/org.jboss.threads/jboss-threads/native-image.properties
// Please make sure to update that file if you remove or rename this class, or if runtime
@@ -330,11 +336,11 @@ private static final class RuntimeFields {
private static final int unsharedTaskNodesSize;
private static final int unsharedLongsSize;
- private static final long headOffset;
- private static final long tailOffset;
+ private static final int headIndex;
+ private static final int tailIndex;
- private static final long threadStatusOffset;
- private static final long queueSizeOffset;
+ private static final int threadStatusIndex;
+ private static final int queueSizeIndex;
static {
int cacheLine = CacheInfo.getSmallestDataCacheLineSize();
@@ -344,28 +350,16 @@ private static final class RuntimeFields {
}
// cpu spatial prefetcher can drag 2 cache-lines at once into L2
int pad = cacheLine > 128 ? cacheLine : 128;
- int longScale = unsafe.arrayIndexScale(long[].class);
- int taskNodeScale = unsafe.arrayIndexScale(TaskNode[].class);
+ // make some assumptions about array scale
+ int longScale = 8;
+ int taskNodeScale = 8;
// these fields are in units of array scale
unsharedTaskNodesSize = pad / taskNodeScale * (numUnsharedObjects + 1);
unsharedLongsSize = pad / longScale * (numUnsharedLongs + 1);
- // these fields are in bytes
- headOffset = unsafe.arrayBaseOffset(TaskNode[].class) + pad;
- tailOffset = unsafe.arrayBaseOffset(TaskNode[].class) + pad * 2;
- threadStatusOffset = unsafe.arrayBaseOffset(long[].class) + pad;
- queueSizeOffset = unsafe.arrayBaseOffset(long[].class) + pad * 2;
- }
- }
-
- static {
- try {
- terminationWaitersOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("terminationWaiters"));
-
- peakThreadCountOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("peakThreadCount"));
- activeCountOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("activeCount"));
- peakQueueSizeOffset = unsafe.objectFieldOffset(EnhancedQueueExecutor.class.getDeclaredField("peakQueueSize"));
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
+ headIndex = pad / taskNodeScale;
+ tailIndex = pad / taskNodeScale * 2;
+ threadStatusIndex = pad / longScale;
+ queueSizeIndex = pad / longScale * 2;
}
}
@@ -383,7 +377,6 @@ private static final class RuntimeFields {
private static final long TS_ALLOW_CORE_TIMEOUT = 1L << 60;
private static final long TS_SHUTDOWN_REQUESTED = 1L << 61;
private static final long TS_SHUTDOWN_INTERRUPT = 1L << 62;
- @SuppressWarnings("NumericOverflow")
private static final long TS_SHUTDOWN_COMPLETE = 1L << 63;
private static final int EXE_OK = 0;
@@ -929,8 +922,7 @@ public List shutdownNow() {
head = getHead(unsharedTaskNodes);
continue;
}
- if (headNext instanceof TaskNode) {
- TaskNode taskNode = (TaskNode) headNext;
+ if (headNext instanceof TaskNode taskNode) {
if (compareAndSetHead(unsharedTaskNodes, head, taskNode)) {
// save from GC nepotism
head.setNextOrdered(head);
@@ -1941,8 +1933,7 @@ private int tryExecute(final Task runnable) {
}
// otherwise the consumer gave up or was exited already, so fall out and...
}
- } else if (tailNext instanceof TaskNode) {
- TaskNode tailNextTaskNode = (TaskNode) tailNext;
+ } else if (tailNext instanceof TaskNode tailNextTaskNode) {
// Opportunistically update tail to the next node. If this operation has been handled by
// another thread we fall back to the loop and try again instead of duplicating effort.
if (compareAndSetTail(tail, tailNextTaskNode)) {
@@ -2013,79 +2004,79 @@ void completeTermination() {
// =======================================================
TaskNode getTail() {
- return (TaskNode) unsafe.getObjectVolatile(unsharedTaskNodes, RuntimeFields.tailOffset);
+ return (TaskNode) objArrayHandle.getVolatile(unsharedTaskNodes, RuntimeFields.tailIndex);
}
TaskNode setTailPlain(TaskNode tail) {
- unsafe.putObject(unsharedTaskNodes, RuntimeFields.tailOffset, tail);
+ unsharedTaskNodes[RuntimeFields.tailIndex] = tail;
return tail;
}
boolean compareAndSetTail(final EnhancedQueueExecutor.TaskNode expect, final EnhancedQueueExecutor.TaskNode update) {
- return getTail() == expect && unsafe.compareAndSwapObject(unsharedTaskNodes, RuntimeFields.tailOffset, expect, update);
+ return getTail() == expect && objArrayHandle.compareAndSet(unsharedTaskNodes, RuntimeFields.tailIndex, expect, update);
}
- TaskNode getHead(final TaskNode[] unsharedTaskNodes) {
- return (TaskNode) unsafe.getObjectVolatile(unsharedTaskNodes, RuntimeFields.headOffset);
+ static TaskNode getHead(final TaskNode[] unsharedTaskNodes) {
+ return (TaskNode) objArrayHandle.getVolatile(unsharedTaskNodes, RuntimeFields.headIndex);
}
TaskNode setHeadPlain(TaskNode head) {
- unsafe.putObject(unsharedTaskNodes, RuntimeFields.headOffset, head);
+ unsharedTaskNodes[RuntimeFields.headIndex] = head;
return head;
}
- boolean compareAndSetHead(final TaskNode[] unsharedTaskNodes, final TaskNode expect, final TaskNode update) {
- return unsafe.compareAndSwapObject(unsharedTaskNodes, RuntimeFields.headOffset, expect, update);
+ static boolean compareAndSetHead(final TaskNode[] unsharedTaskNodes, final TaskNode expect, final TaskNode update) {
+ return objArrayHandle.compareAndSet(unsharedTaskNodes, RuntimeFields.headIndex, expect, update);
}
long getThreadStatus() {
- return unsafe.getLongVolatile(unsharedLongs, RuntimeFields.threadStatusOffset);
+ return (long) longArrayHandle.getVolatile(unsharedLongs, RuntimeFields.threadStatusIndex);
}
long setThreadStatusPlain(long status) {
- unsafe.putLong(unsharedLongs, RuntimeFields.threadStatusOffset, status);
+ unsharedLongs[RuntimeFields.threadStatusIndex] = status;
return status;
}
boolean compareAndSetThreadStatus(final long expect, final long update) {
- return unsafe.compareAndSwapLong(unsharedLongs, RuntimeFields.threadStatusOffset, expect, update);
+ return longArrayHandle.compareAndSet(unsharedLongs, RuntimeFields.threadStatusIndex, expect, update);
}
void incrementActiveCount() {
- unsafe.getAndAddInt(this, activeCountOffset, 1);
+ activeCountHandle.getAndAdd(this, 1);
}
void decrementActiveCount() {
- unsafe.getAndAddInt(this, activeCountOffset, -1);
+ activeCountHandle.getAndAdd(this, -1);
}
boolean compareAndSetPeakThreadCount(final int expect, final int update) {
- return unsafe.compareAndSwapInt(this, peakThreadCountOffset, expect, update);
+ return peakThreadCountHandle.compareAndSet(this, expect, update);
}
boolean compareAndSetPeakQueueSize(final int expect, final int update) {
- return unsafe.compareAndSwapInt(this, peakQueueSizeOffset, expect, update);
+ return peakQueueSizeHandle.compareAndSet(this, expect, update);
}
long getQueueSizeVolatile() {
- return unsafe.getLongVolatile(unsharedLongs, RuntimeFields.queueSizeOffset);
+ return (long) longArrayHandle.getVolatile(unsharedLongs, RuntimeFields.queueSizeIndex);
}
long setQueueSizePlain(long queueSize) {
- unsafe.putLong(unsharedLongs, RuntimeFields.queueSizeOffset, queueSize);
+ unsharedLongs[RuntimeFields.queueSizeIndex] = queueSize;
return queueSize;
}
boolean compareAndSetQueueSize(final long expect, final long update) {
- return unsafe.compareAndSwapLong(unsharedLongs, RuntimeFields.queueSizeOffset, expect, update);
+ return longArrayHandle.compareAndSet(unsharedLongs, RuntimeFields.queueSizeIndex, expect, update);
}
boolean compareAndSetTerminationWaiters(final Waiter expect, final Waiter update) {
- return unsafe.compareAndSwapObject(this, terminationWaitersOffset, expect, update);
+ return terminationWaitersHandle.compareAndSet(this, expect, update);
}
Waiter getAndSetTerminationWaiters(final Waiter update) {
- return (Waiter) unsafe.getAndSetObject(this, terminationWaitersOffset, update);
+ return (Waiter) terminationWaitersHandle.getAndSet(this, update);
}
// =======================================================
@@ -2294,21 +2285,13 @@ void rejectShutdown(final Task task) {
// =======================================================
abstract static class QNode {
- private static final long nextOffset;
-
- static {
- try {
- nextOffset = unsafe.objectFieldOffset(QNode.class.getDeclaredField("next"));
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
- }
- }
+ private static final VarHandle nextHandle = ConstantBootstraps.fieldVarHandle(lookup(), "next", VarHandle.class, QNode.class, QNode.class);
@SuppressWarnings("unused")
private volatile QNode next;
boolean compareAndSetNext(QNode expect, QNode update) {
- return unsafe.compareAndSwapObject(this, nextOffset, expect, update);
+ return nextHandle.compareAndSet(this, expect, update);
}
QNode getNext() {
@@ -2320,11 +2303,11 @@ void setNext(final QNode node) {
}
void setNextRelaxed(final QNode node) {
- unsafe.putObject(this, nextOffset, node);
+ nextHandle.set(this, node);
}
void setNextOrdered(final QNode node) {
- unsafe.putOrderedObject(this, nextOffset, node);
+ nextHandle.setOpaque(this, node);
}
}
@@ -2360,18 +2343,8 @@ static final class PoolThreadNode extends PoolThreadNodeBase {
*/
private static final int STATE_UNPARKED = 2;
-
- private static final long taskOffset;
- private static final long parkedOffset;
-
- static {
- try {
- taskOffset = unsafe.objectFieldOffset(PoolThreadNode.class.getDeclaredField("task"));
- parkedOffset = unsafe.objectFieldOffset(PoolThreadNode.class.getDeclaredField("parked"));
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
- }
- }
+ private static final VarHandle taskHandle = ConstantBootstraps.fieldVarHandle(lookup(), "task", VarHandle.class, PoolThreadNode.class, Runnable.class);
+ private static final VarHandle parkedHandle = ConstantBootstraps.fieldVarHandle(lookup(), "parked", VarHandle.class, PoolThreadNode.class, int.class);
private final Thread thread;
@@ -2390,7 +2363,7 @@ static final class PoolThreadNode extends PoolThreadNodeBase {
}
boolean compareAndSetTask(final Runnable expect, final Runnable update) {
- return task == expect && unsafe.compareAndSwapObject(this, taskOffset, expect, update);
+ return task == expect && taskHandle.compareAndSet(this, expect, update);
}
Runnable getTask() {
@@ -2403,7 +2376,7 @@ PoolThreadNode getNext() {
void park(EnhancedQueueExecutor enhancedQueueExecutor) {
int spins = PARK_SPINS;
- if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) {
+ if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) {
return;
}
while (spins > 0) {
@@ -2413,11 +2386,11 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor) {
Thread.onSpinWait();
}
spins--;
- if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) {
+ if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) {
return;
}
}
- if (parked == STATE_NORMAL && unsafe.compareAndSwapInt(this, parkedOffset, STATE_NORMAL, STATE_PARKED)) try {
+ if (parked == STATE_NORMAL && parkedHandle.compareAndSet(this, STATE_NORMAL, STATE_PARKED)) try {
LockSupport.park(enhancedQueueExecutor);
} finally {
// parked can be STATE_PARKED or STATE_UNPARKED, cannot possibly be STATE_NORMAL.
@@ -2436,7 +2409,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) {
//as spin time is short and for our use cases it does not matter if the time
//overruns a bit (as the nano time is for thread timeout) we just spin then check
//to keep performance consistent between the two versions.
- if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) {
+ if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) {
return;
}
while (spins > 0) {
@@ -2445,7 +2418,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) {
} else {
Thread.onSpinWait();
}
- if (parked == STATE_UNPARKED && unsafe.compareAndSwapInt(this, parkedOffset, STATE_UNPARKED, STATE_NORMAL)) {
+ if (parked == STATE_UNPARKED && parkedHandle.compareAndSet(this, STATE_UNPARKED, STATE_NORMAL)) {
return;
}
spins--;
@@ -2457,7 +2430,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) {
} else {
remaining = nanos;
}
- if (parked == STATE_NORMAL && unsafe.compareAndSwapInt(this, parkedOffset, STATE_NORMAL, STATE_PARKED)) try {
+ if (parked == STATE_NORMAL && parkedHandle.compareAndSet(this, STATE_NORMAL, STATE_PARKED)) try {
LockSupport.parkNanos(enhancedQueueExecutor, remaining);
} finally {
// parked can be STATE_PARKED or STATE_UNPARKED, cannot possibly be STATE_NORMAL.
@@ -2468,7 +2441,7 @@ void park(EnhancedQueueExecutor enhancedQueueExecutor, long nanos) {
}
void unpark() {
- if (parked == STATE_NORMAL && unsafe.compareAndSwapInt(this, parkedOffset, STATE_NORMAL, STATE_UNPARKED)) {
+ if (parked == STATE_NORMAL && parkedHandle.compareAndSet(this, STATE_NORMAL, STATE_UNPARKED)) {
return;
}
LockSupport.unpark(thread);
diff --git a/src/main/java/org/jboss/threads/EnhancedViewExecutor.java b/src/main/java/org/jboss/threads/EnhancedViewExecutor.java
index 5118d8bf..908d07fc 100644
--- a/src/main/java/org/jboss/threads/EnhancedViewExecutor.java
+++ b/src/main/java/org/jboss/threads/EnhancedViewExecutor.java
@@ -5,6 +5,8 @@
import io.smallrye.common.constraint.Nullable;
import io.smallrye.common.cpu.ProcessorInfo;
+import java.lang.invoke.ConstantBootstraps;
+import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -18,7 +20,7 @@
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
-import static org.jboss.threads.JBossExecutors.unsafe;
+import static java.lang.invoke.MethodHandles.lookup;
/**
* A View Executor implementation which avoids lock contention in the common path. This allows us to
@@ -29,14 +31,7 @@
*/
final class EnhancedViewExecutor extends ViewExecutor {
private static final Logger log = Logger.getLogger("org.jboss.threads.view-executor");
- private static final long stateOffset;
- static {
- try {
- stateOffset = unsafe.objectFieldOffset(EnhancedViewExecutor.class.getDeclaredField("state"));
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
- }
- }
+ private static final VarHandle stateHandle = ConstantBootstraps.fieldVarHandle(lookup(), "state", VarHandle.class, EnhancedViewExecutor.class, long.class);
private static final int QUEUE_FAILURE_LOG_INTERVAL =
readIntPropertyPrefixed("queue.failure.log.interval", 1_000_000);
@@ -429,7 +424,7 @@ private static int getQueueSize(long state) {
}
private boolean compareAndSwapState(long expected, long update) {
- return unsafe.compareAndSwapLong(this, stateOffset, expected, update);
+ return stateHandle.compareAndSet(this, expected, update);
}
private Thread.UncaughtExceptionHandler uncaughtExceptionHandler() {
diff --git a/src/main/java/org/jboss/threads/JBossExecutors.java b/src/main/java/org/jboss/threads/JBossExecutors.java
index 8b8f00cb..b4aca73a 100644
--- a/src/main/java/org/jboss/threads/JBossExecutors.java
+++ b/src/main/java/org/jboss/threads/JBossExecutors.java
@@ -1,23 +1,18 @@
package org.jboss.threads;
-import java.lang.reflect.Field;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ScheduledExecutorService;
-import java.security.PrivilegedAction;
-import java.security.AccessController;
import org.jboss.logging.Logger;
import io.smallrye.common.constraint.Assert;
-import sun.misc.Unsafe;
/**
* JBoss thread- and executor-related utility and factory methods.
*/
-@SuppressWarnings("deprecation")
public final class JBossExecutors {
private static final Logger THREAD_ERROR_LOGGER = Logger.getLogger("org.jboss.threads.errors");
@@ -225,7 +220,7 @@ public Thread newThread(final Runnable r) {
private static final Runnable TCCL_RESETTER = new Runnable() {
public void run() {
- Thread.currentThread().setContextClassLoader(null);
+ JDKSpecific.setThreadContextClassLoader(Thread.currentThread(), null);
}
public String toString() {
@@ -294,31 +289,6 @@ static Runnable classLoaderPreservingTaskUnchecked(final Runnable delegate) {
return new ContextClassLoaderSavingRunnable(getContextClassLoader(Thread.currentThread()), delegate);
}
- static final Unsafe unsafe;
-
- static final long contextClassLoaderOffs;
-
- static {
- unsafe = AccessController.doPrivileged(new PrivilegedAction() {
- public Unsafe run() {
- try {
- final Field field = Unsafe.class.getDeclaredField("theUnsafe");
- field.setAccessible(true);
- return (Unsafe) field.get(null);
- } catch (IllegalAccessException e) {
- throw new IllegalAccessError(e.getMessage());
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
- }
- }
- });
- try {
- contextClassLoaderOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("contextClassLoader"));
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
- }
- }
-
/**
* Privileged method to get the context class loader of the given thread.
*
@@ -326,7 +296,7 @@ public Unsafe run() {
* @return the context class loader
*/
static ClassLoader getContextClassLoader(final Thread thread) {
- return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
+ return JDKSpecific.getThreadContextClassLoader(thread);
}
/**
@@ -337,12 +307,11 @@ static ClassLoader getContextClassLoader(final Thread thread) {
* @return the old context class loader
*/
static ClassLoader getAndSetContextClassLoader(final Thread thread, final ClassLoader newClassLoader) {
- final ClassLoader currentClassLoader = (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
- if (currentClassLoader != newClassLoader) {
- // not using setContextClassLoader to save loading the current one again
- unsafe.putObject(thread, contextClassLoaderOffs, newClassLoader);
+ ClassLoader old = JDKSpecific.getThreadContextClassLoader(thread);
+ if (old != newClassLoader) {
+ JDKSpecific.setThreadContextClassLoader(thread, newClassLoader);
}
- return currentClassLoader;
+ return old;
}
/**
@@ -352,9 +321,7 @@ static ClassLoader getAndSetContextClassLoader(final Thread thread, final ClassL
* @param classLoader the new context class loader
*/
static void setContextClassLoader(final Thread thread, final ClassLoader classLoader) {
- if (unsafe.getObject(thread, contextClassLoaderOffs) != classLoader) {
- unsafe.putObject(thread, contextClassLoaderOffs, classLoader);
- }
+ JDKSpecific.setThreadContextClassLoader(thread, classLoader);
}
/**
@@ -363,7 +330,7 @@ static void setContextClassLoader(final Thread thread, final ClassLoader classLo
* @param thread the thread to introspect
*/
static void clearContextClassLoader(final Thread thread) {
- unsafe.putObject(thread, contextClassLoaderOffs, SAFE_CL);
+ JDKSpecific.setThreadContextClassLoader(thread, SAFE_CL);
}
// ==================================================
diff --git a/src/main/java/org/jboss/threads/JDKSpecific.java b/src/main/java/org/jboss/threads/JDKSpecific.java
new file mode 100644
index 00000000..d4eaa1f4
--- /dev/null
+++ b/src/main/java/org/jboss/threads/JDKSpecific.java
@@ -0,0 +1,71 @@
+package org.jboss.threads;
+
+import java.lang.reflect.Field;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+
+import sun.misc.Unsafe;
+
+final class JDKSpecific {
+ private JDKSpecific() {}
+
+ private static final Unsafe unsafe;
+ private static final long contextClassLoaderOffs;
+
+ static {
+ unsafe = AccessController.doPrivileged(new PrivilegedAction() {
+ public Unsafe run() {
+ try {
+ Field field = Unsafe.class.getDeclaredField("theUnsafe");
+ field.setAccessible(true);
+ return (Unsafe) field.get(null);
+ } catch (IllegalAccessException e) {
+ IllegalAccessError error = new IllegalAccessError(e.getMessage());
+ error.setStackTrace(e.getStackTrace());
+ throw error;
+ } catch (NoSuchFieldException e) {
+ NoSuchFieldError error = new NoSuchFieldError(e.getMessage());
+ error.setStackTrace(e.getStackTrace());
+ throw error;
+ }
+ }
+ });
+ try {
+ contextClassLoaderOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("contextClassLoader"));
+ } catch (NoSuchFieldException e) {
+ NoSuchFieldError error = new NoSuchFieldError(e.getMessage());
+ error.setStackTrace(e.getStackTrace());
+ throw error;
+ }
+ }
+
+ static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) {
+ unsafe.putObject(thread, contextClassLoaderOffs, classLoader);
+ }
+
+ static ClassLoader getThreadContextClassLoader(Thread thread) {
+ return (ClassLoader) unsafe.getObject(thread, contextClassLoaderOffs);
+ }
+
+ static final class ThreadAccess {
+ private static final long threadLocalMapOffs;
+ private static final long inheritableThreadLocalMapOffs;
+
+ static {
+ try {
+ threadLocalMapOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals"));
+ inheritableThreadLocalMapOffs = unsafe.objectFieldOffset(Thread.class.getDeclaredField("inheritableThreadLocals"));
+ } catch (NoSuchFieldException e) {
+ NoSuchFieldError error = new NoSuchFieldError(e.getMessage());
+ error.setStackTrace(e.getStackTrace());
+ throw error;
+ }
+ }
+
+ static void clearThreadLocals() {
+ Thread thread = Thread.currentThread();
+ unsafe.putObject(thread, threadLocalMapOffs, null);
+ unsafe.putObject(thread, inheritableThreadLocalMapOffs, null);
+ }
+ }
+}
diff --git a/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java b/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java
index 5536ef32..123fefe7 100644
--- a/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java
+++ b/src/main/java/org/jboss/threads/ThreadLocalResettingRunnable.java
@@ -10,31 +10,11 @@ public void run() {
try {
super.run();
} finally {
- Resetter.run();
+ JDKSpecific.ThreadAccess.clearThreadLocals();
}
}
public String toString() {
return "Thread-local resetting Runnable";
}
-
- static final class Resetter {
- private static final long threadLocalMapOffs;
- private static final long inheritableThreadLocalMapOffs;
-
- static {
- try {
- threadLocalMapOffs = JBossExecutors.unsafe.objectFieldOffset(Thread.class.getDeclaredField("threadLocals"));
- inheritableThreadLocalMapOffs = JBossExecutors.unsafe.objectFieldOffset(Thread.class.getDeclaredField("inheritableThreadLocals"));
- } catch (NoSuchFieldException e) {
- throw new NoSuchFieldError(e.getMessage());
- }
- }
-
- static void run() {
- final Thread thread = Thread.currentThread();
- JBossExecutors.unsafe.putObject(thread, threadLocalMapOffs, null);
- JBossExecutors.unsafe.putObject(thread, inheritableThreadLocalMapOffs, null);
- }
- }
}
diff --git a/src/main/java24/org/jboss/threads/JDKSpecific.java b/src/main/java24/org/jboss/threads/JDKSpecific.java
new file mode 100644
index 00000000..6467e5c0
--- /dev/null
+++ b/src/main/java24/org/jboss/threads/JDKSpecific.java
@@ -0,0 +1,51 @@
+package org.jboss.threads;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.UndeclaredThrowableException;
+
+final class JDKSpecific {
+ private JDKSpecific() {}
+
+ static void setThreadContextClassLoader(Thread thread, ClassLoader classLoader) {
+ thread.setContextClassLoader(classLoader);
+ }
+
+ static ClassLoader getThreadContextClassLoader(Thread thread) {
+ return thread.getContextClassLoader();
+ }
+
+ static final class ThreadAccess {
+ private static final MethodHandle setThreadLocalsHandle;
+ private static final MethodHandle setInheritableThreadLocalsHandle;
+
+ static {
+ try {
+ MethodHandles.Lookup threadLookup = MethodHandles.privateLookupIn(Thread.class, MethodHandles.lookup());
+ setThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("threadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Object.class));
+ setInheritableThreadLocalsHandle = threadLookup.unreflectVarHandle(Thread.class.getDeclaredField("inheritableThreadLocals")).toMethodHandle(VarHandle.AccessMode.SET).asType(MethodType.methodType(void.class, Object.class));
+ } catch (IllegalAccessException e) {
+ Module myModule = ThreadAccess.class.getModule();
+ String myName = myModule.isNamed() ? myModule.getName() : "ALL-UNNAMED";
+ throw new IllegalAccessError(e.getMessage() +
+ "; to use the thread-local-reset capability on Java 24 or later, use this JVM option: --add-opens java.base/java.lang=" + myName);
+ } catch (NoSuchFieldException e) {
+ throw new NoSuchFieldError(e.getMessage());
+ }
+ }
+
+ static void clearThreadLocals() {
+ final Thread thread = Thread.currentThread();
+ try {
+ setThreadLocalsHandle.invokeExact(thread, null);
+ setInheritableThreadLocalsHandle.invokeExact(thread, null);
+ } catch (RuntimeException | Error e) {
+ throw e;
+ } catch (Throwable t) {
+ throw new UndeclaredThrowableException(t);
+ }
+ }
+ }
+}