< prev index next > src/java.base/share/classes/java/lang/ThreadLocal.java
Print this page
import java.lang.ref.WeakReference;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
+ import java.util.stream.Collectors;
import jdk.internal.misc.CarrierThreadLocal;
import jdk.internal.misc.TerminatingThreadLocal;
+ import sun.security.action.GetPropertyAction;
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
public class ThreadLocal<T> {
+ private static final boolean TRACE_VTHREAD_LOCALS = traceVirtualThreadLocals();
+
/**
* ThreadLocals rely on per-thread linear-probe hash maps attached
* to each thread (Thread.threadLocals and
* inheritableThreadLocals). The ThreadLocal objects act as keys,
* searched via threadLocalHashCode. This is a custom hash code
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
- * If the current thread does not support thread locals then
- * this method returns its {@link #initialValue}.
*
* @return the current thread's value of this thread-local
- * @see Thread.Builder#allowSetThreadLocals(boolean)
*/
public T get() {
return get(Thread.currentThread());
}
}
private T get(Thread t) {
ThreadLocalMap map = getMap(t);
if (map != null) {
- if (map == ThreadLocalMap.NOT_SUPPORTED) {
- return initialValue();
- } else {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T) e.value;
- return result;
- }
+ ThreadLocalMap.Entry e = map.getEntry(this);
+ if (e != null) {
+ @SuppressWarnings("unchecked")
+ T result = (T) e.value;
+ return result;
}
}
return setInitialValue(t);
}
return isPresent(Thread.currentCarrierThread());
}
private boolean isPresent(Thread t) {
ThreadLocalMap map = getMap(t);
- if (map != null && map != ThreadLocalMap.NOT_SUPPORTED) {
+ if (map != null) {
return map.getEntry(this) != null;
} else {
return false;
}
}
* @return the initial value
*/
private T setInitialValue(Thread t) {
T value = initialValue();
ThreadLocalMap map = getMap(t);
- assert map != ThreadLocalMap.NOT_SUPPORTED;
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal<?> ttl) {
TerminatingThreadLocal.register(ttl);
}
+ if (TRACE_VTHREAD_LOCALS) {
+ dumpStackIfVirtualThread();
+ }
return value;
}
/**
* Sets the current thread's copy of this thread-local variable
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
- *
- * @throws UnsupportedOperationException if the current thread is not
- * allowed to set its copy of thread-local variables
- *
- * @see Thread.Builder#allowSetThreadLocals(boolean)
*/
public void set(T value) {
set(Thread.currentThread(), value);
+ if (TRACE_VTHREAD_LOCALS) {
+ dumpStackIfVirtualThread();
+ }
}
void setCarrierThreadLocal(T value) {
assert this instanceof CarrierThreadLocal<T>;
set(Thread.currentCarrierThread(), value);
}
private void set(Thread t, T value) {
ThreadLocalMap map = getMap(t);
- if (map == ThreadLocalMap.NOT_SUPPORTED) {
- throw new UnsupportedOperationException();
- }
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
remove(Thread.currentCarrierThread());
}
private void remove(Thread t) {
ThreadLocalMap m = getMap(t);
- if (m != null && m != ThreadLocalMap.NOT_SUPPORTED) {
+ if (m != null) {
m.remove(this);
}
}
/**
super(k);
value = v;
}
}
- // Placeholder when thread locals not supported
- static final ThreadLocalMap NOT_SUPPORTED = new ThreadLocalMap();
-
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
if (e != null && e.refersTo(null))
expungeStaleEntry(j);
}
}
}
+
+
+ /**
+ * Reads the value of the jdk.traceVirtualThreadLocals property to determine if
+ * a stack trace should be printed when a virtual threads sets a thread local.
+ */
+ private static boolean traceVirtualThreadLocals() {
+ String propValue = GetPropertyAction.privilegedGetProperty("jdk.traceVirtualThreadLocals");
+ return (propValue != null)
+ && (propValue.isEmpty() || Boolean.parseBoolean(propValue));
+ }
+
+ /**
+ * Print a stack trace if the current thread is a virtual thread.
+ */
+ static void dumpStackIfVirtualThread() {
+ if (Thread.currentThread() instanceof VirtualThread vthread) {
+ try {
+ var stack = StackWalkerHolder.STACK_WALKER.walk(s ->
+ s.skip(1) // skip caller
+ .collect(Collectors.toList()));
+
+ // switch to carrier thread to avoid recursive use of thread-locals
+ vthread.executeOnCarrierThread(() -> {
+ System.out.println(vthread);
+ for (StackWalker.StackFrame frame : stack) {
+ System.out.format(" %s%n", frame.toStackTraceElement());
+ }
+ return null;
+ });
+ } catch (Exception e) {
+ throw new InternalError(e);
+ }
+ }
+ }
+
+ private static class StackWalkerHolder {
+ static final StackWalker STACK_WALKER = StackWalker.getInstance();
+ }
}
< prev index next >