< prev index next >

src/java.base/share/classes/java/lang/ThreadLocal.java

Print this page
@@ -27,13 +27,15 @@
  
  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

@@ -75,10 +77,12 @@
   *
   * @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

@@ -159,15 +163,12 @@
      /**
       * 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());
      }
  

@@ -181,19 +182,15 @@
      }
  
      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);
      }
  

@@ -209,11 +206,11 @@
          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;
          }
      }

@@ -225,19 +222,21 @@
       * @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

@@ -245,30 +244,25 @@
       * 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);
          }

@@ -294,11 +288,11 @@
           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);
           }
       }
  
      /**

@@ -392,13 +386,10 @@
                  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;
  

@@ -805,6 +796,45 @@
                  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 >