View Javadoc
1   /*
2   * Copyright 2014 The Netty Project
3   *
4   * The Netty Project licenses this file to you under the Apache License,
5   * version 2.0 (the "License"); you may not use this file except in compliance
6   * with the License. You may obtain a copy of the License at:
7   *
8   *   https://www.apache.org/licenses/LICENSE-2.0
9   *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations
14  * under the License.
15  */
16  package io.netty.util.concurrent;
17  
18  import io.netty.util.internal.InternalThreadLocalMap;
19  import io.netty.util.internal.logging.InternalLogger;
20  import io.netty.util.internal.logging.InternalLoggerFactory;
21  
22  import java.util.Arrays;
23  import java.util.concurrent.atomic.AtomicReference;
24  
25  /**
26   * A special {@link Thread} that provides fast access to {@link FastThreadLocal} variables.
27   */
28  public class FastThreadLocalThread extends Thread {
29  
30      private static final InternalLogger logger = InternalLoggerFactory.getInstance(FastThreadLocalThread.class);
31  
32      /**
33       * Sorted array of thread IDs that are treated like {@link FastThreadLocalThread}.
34       */
35      private static final AtomicReference<long[]> fallbackThreads = new AtomicReference<>(null);
36  
37      // This will be set to true if we have a chance to wrap the Runnable.
38      private final boolean cleanupFastThreadLocals;
39  
40      private InternalThreadLocalMap threadLocalMap;
41  
42      public FastThreadLocalThread() {
43          cleanupFastThreadLocals = false;
44      }
45  
46      public FastThreadLocalThread(Runnable target) {
47          super(FastThreadLocalRunnable.wrap(target));
48          cleanupFastThreadLocals = true;
49      }
50  
51      public FastThreadLocalThread(ThreadGroup group, Runnable target) {
52          super(group, FastThreadLocalRunnable.wrap(target));
53          cleanupFastThreadLocals = true;
54      }
55  
56      public FastThreadLocalThread(String name) {
57          super(name);
58          cleanupFastThreadLocals = false;
59      }
60  
61      public FastThreadLocalThread(ThreadGroup group, String name) {
62          super(group, name);
63          cleanupFastThreadLocals = false;
64      }
65  
66      public FastThreadLocalThread(Runnable target, String name) {
67          super(FastThreadLocalRunnable.wrap(target), name);
68          cleanupFastThreadLocals = true;
69      }
70  
71      public FastThreadLocalThread(ThreadGroup group, Runnable target, String name) {
72          super(group, FastThreadLocalRunnable.wrap(target), name);
73          cleanupFastThreadLocals = true;
74      }
75  
76      public FastThreadLocalThread(ThreadGroup group, Runnable target, String name, long stackSize) {
77          super(group, FastThreadLocalRunnable.wrap(target), name, stackSize);
78          cleanupFastThreadLocals = true;
79      }
80  
81      /**
82       * Returns the internal data structure that keeps the thread-local variables bound to this thread.
83       * Note that this method is for internal use only, and thus is subject to change at any time.
84       */
85      public final InternalThreadLocalMap threadLocalMap() {
86          if (this != Thread.currentThread() && logger.isWarnEnabled()) {
87              logger.warn(new RuntimeException("It's not thread-safe to get 'threadLocalMap' " +
88                      "which doesn't belong to the caller thread"));
89          }
90          return threadLocalMap;
91      }
92  
93      /**
94       * Sets the internal data structure that keeps the thread-local variables bound to this thread.
95       * Note that this method is for internal use only, and thus is subject to change at any time.
96       */
97      public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
98          if (this != Thread.currentThread() && logger.isWarnEnabled()) {
99              logger.warn(new RuntimeException("It's not thread-safe to set 'threadLocalMap' " +
100                     "which doesn't belong to the caller thread"));
101         }
102         this.threadLocalMap = threadLocalMap;
103     }
104 
105     /**
106      * Returns {@code true} if {@link FastThreadLocal#removeAll()} will be called once {@link #run()} completes.
107      *
108      * @deprecated Use {@link FastThreadLocalThread#currentThreadWillCleanupFastThreadLocals()} instead
109      */
110     @Deprecated
111     public boolean willCleanupFastThreadLocals() {
112         return cleanupFastThreadLocals;
113     }
114 
115     /**
116      * Returns {@code true} if {@link FastThreadLocal#removeAll()} will be called once {@link Thread#run()} completes.
117      *
118      * @deprecated Use {@link FastThreadLocalThread#currentThreadWillCleanupFastThreadLocals()} instead
119      */
120     @Deprecated
121     public static boolean willCleanupFastThreadLocals(Thread thread) {
122         return thread instanceof FastThreadLocalThread &&
123                 ((FastThreadLocalThread) thread).willCleanupFastThreadLocals();
124     }
125 
126     /**
127      * Returns {@code true} if {@link FastThreadLocal#removeAll()} will be called once {@link Thread#run()} completes.
128      */
129     public static boolean currentThreadWillCleanupFastThreadLocals() {
130         // intentionally doesn't accept a thread parameter to work with ScopedValue in the future
131         Thread currentThread = currentThread();
132         if (currentThread instanceof FastThreadLocalThread) {
133             return ((FastThreadLocalThread) currentThread).willCleanupFastThreadLocals();
134         }
135         return isFastThreadLocalVirtualThread();
136     }
137 
138     /**
139      * Returns {@code true} if this thread supports {@link FastThreadLocal}.
140      */
141     public static boolean currentThreadHasFastThreadLocal() {
142         // intentionally doesn't accept a thread parameter to work with ScopedValue in the future
143         return currentThread() instanceof FastThreadLocalThread || isFastThreadLocalVirtualThread();
144     }
145 
146     private static boolean isFastThreadLocalVirtualThread() {
147         long[] arr = fallbackThreads.get();
148         if (arr == null) {
149             return false;
150         }
151         return Arrays.binarySearch(arr, Thread.currentThread().getId()) >= 0;
152     }
153 
154     /**
155      * Run the given task with {@link FastThreadLocal} support. This call should wrap the runnable for any thread that
156      * is long-running enough to make treating it as a {@link FastThreadLocalThread} reasonable, but that can't
157      * actually extend this class (e.g. because it's a virtual thread). Netty will use optimizations for recyclers and
158      * allocators as if this was a {@link FastThreadLocalThread}.
159      * <p>This method will clean up any {@link FastThreadLocal}s at the end, and
160      * {@link #currentThreadWillCleanupFastThreadLocals()} will return {@code true}.
161      * <p>At the moment, {@link FastThreadLocal} uses normal {@link ThreadLocal} as the backing storage here, but in
162      * the future this may be replaced with scoped values, if semantics can be preserved and performance is good.
163      *
164      * @param runnable The task to run
165      */
166     public static void runWithFastThreadLocal(Runnable runnable) {
167         Thread current = currentThread();
168         if (current instanceof FastThreadLocalThread) {
169             throw new IllegalStateException("Caller is a real FastThreadLocalThread");
170         }
171         long id = current.getId();
172         fallbackThreads.updateAndGet(arr -> {
173             if (arr == null) {
174                 return new long[] { id };
175             }
176             int index = Arrays.binarySearch(arr, id);
177             if (index >= 0) {
178                 throw new IllegalStateException("Reentrant call to run()");
179             }
180             index = ~index; // same as -(index + 1)
181             long[] next = new long[arr.length + 1];
182             System.arraycopy(arr, 0, next, 0, index);
183             next[index] = id;
184             System.arraycopy(arr, index, next, index + 1, arr.length - index);
185             return next;
186         });
187         try {
188             runnable.run();
189         } finally {
190             fallbackThreads.getAndUpdate(arr -> {
191                 if (arr == null || (arr.length == 1 && arr[0] == id)) {
192                     return null;
193                 }
194                 int index = Arrays.binarySearch(arr, id);
195                 if (index < 0) {
196                     return arr;
197                 }
198                 long[] next = new long[arr.length - 1];
199                 System.arraycopy(arr, 0, next, 0, index);
200                 System.arraycopy(arr, index + 1, next, index, arr.length - index - 1);
201                 return next;
202             });
203             FastThreadLocal.removeAll();
204         }
205     }
206 
207     /**
208      * Query whether this thread is allowed to perform blocking calls or not.
209      * {@link FastThreadLocalThread}s are often used in event-loops, where blocking calls are forbidden in order to
210      * prevent event-loop stalls, so this method returns {@code false} by default.
211      * <p>
212      * Subclasses of {@link FastThreadLocalThread} can override this method if they are not meant to be used for
213      * running event-loops.
214      *
215      * @return {@code false}, unless overriden by a subclass.
216      */
217     public boolean permitBlockingCalls() {
218         return false;
219     }
220 }