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.PlatformDependent;
20  
21  import java.util.Collections;
22  import java.util.IdentityHashMap;
23  import java.util.Set;
24  
25  import static io.netty.util.internal.InternalThreadLocalMap.UNSET;
26  import static io.netty.util.internal.InternalThreadLocalMap.VARIABLES_TO_REMOVE_INDEX;
27  
28  /**
29   * A special variant of {@link ThreadLocal} that yields higher access performance when accessed from a
30   * {@link FastThreadLocalThread}.
31   * <p>
32   * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table,
33   * to look for a variable.  Although seemingly very subtle, it yields slight performance advantage over using a hash
34   * table, and it is useful when accessed frequently.
35   * </p><p>
36   * To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype.
37   * By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason.
38   * </p><p>
39   * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires
40   * a special field to store the necessary state.  An access by any other kind of thread falls back to a regular
41   * {@link ThreadLocal}.
42   * </p>
43   *
44   * @param <V> the type of the thread-local variable
45   * @see ThreadLocal
46   */
47  public class FastThreadLocal<V> {
48  
49      /**
50       * Removes all {@link FastThreadLocal} variables bound to the current thread.  This operation is useful when you
51       * are in a container environment, and you don't want to leave the thread local variables in the threads you do not
52       * manage.
53       */
54      public static void removeAll() {
55          InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
56          if (threadLocalMap == null) {
57              return;
58          }
59  
60          try {
61              Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
62              if (v != null && v != InternalThreadLocalMap.UNSET) {
63                  @SuppressWarnings("unchecked")
64                  Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
65                  FastThreadLocal<?>[] variablesToRemoveArray =
66                          variablesToRemove.toArray(new FastThreadLocal[0]);
67                  for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
68                      tlv.remove(threadLocalMap);
69                  }
70              }
71          } finally {
72              InternalThreadLocalMap.remove();
73          }
74      }
75  
76      /**
77       * Returns the number of thread local variables bound to the current thread.
78       */
79      public static int size() {
80          InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
81          if (threadLocalMap == null) {
82              return 0;
83          } else {
84              return threadLocalMap.size();
85          }
86      }
87  
88      /**
89       * Destroys the data structure that keeps all {@link FastThreadLocal} variables accessed from
90       * non-{@link FastThreadLocalThread}s.  This operation is useful when you are in a container environment, and you
91       * do not want to leave the thread local variables in the threads you do not manage.  Call this method when your
92       * application is being unloaded from the container.
93       */
94      public static void destroy() {
95          InternalThreadLocalMap.destroy();
96      }
97  
98      @SuppressWarnings("unchecked")
99      private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
100         Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
101         Set<FastThreadLocal<?>> variablesToRemove;
102         if (v == InternalThreadLocalMap.UNSET || v == null) {
103             variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
104             threadLocalMap.setIndexedVariable(VARIABLES_TO_REMOVE_INDEX, variablesToRemove);
105         } else {
106             variablesToRemove = (Set<FastThreadLocal<?>>) v;
107         }
108 
109         variablesToRemove.add(variable);
110     }
111 
112     private static void removeFromVariablesToRemove(
113             InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
114 
115         Object v = threadLocalMap.indexedVariable(VARIABLES_TO_REMOVE_INDEX);
116 
117         if (v == InternalThreadLocalMap.UNSET || v == null) {
118             return;
119         }
120 
121         @SuppressWarnings("unchecked")
122         Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
123         variablesToRemove.remove(variable);
124     }
125 
126     private final int index;
127 
128     public FastThreadLocal() {
129         index = InternalThreadLocalMap.nextVariableIndex();
130     }
131 
132     /**
133      * Returns the current value for the current thread
134      */
135     @SuppressWarnings("unchecked")
136     public final V get() {
137         InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
138         Object v = threadLocalMap.indexedVariable(index);
139         if (v != InternalThreadLocalMap.UNSET) {
140             return (V) v;
141         }
142 
143         return initialize(threadLocalMap);
144     }
145 
146     /**
147      * Returns the current value for the current thread if it exists, {@code null} otherwise.
148      */
149     @SuppressWarnings("unchecked")
150     public final V getIfExists() {
151         InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
152         if (threadLocalMap != null) {
153             Object v = threadLocalMap.indexedVariable(index);
154             if (v != InternalThreadLocalMap.UNSET) {
155                 return (V) v;
156             }
157         }
158         return null;
159     }
160 
161     /**
162      * Returns the current value for the specified thread local map.
163      * The specified thread local map must be for the current thread.
164      */
165     @SuppressWarnings("unchecked")
166     public final V get(InternalThreadLocalMap threadLocalMap) {
167         Object v = threadLocalMap.indexedVariable(index);
168         if (v != InternalThreadLocalMap.UNSET) {
169             return (V) v;
170         }
171 
172         return initialize(threadLocalMap);
173     }
174 
175     private V initialize(InternalThreadLocalMap threadLocalMap) {
176         V v = null;
177         try {
178             v = initialValue();
179             if (v == InternalThreadLocalMap.UNSET) {
180                 throw new IllegalArgumentException("InternalThreadLocalMap.UNSET can not be initial value.");
181             }
182         } catch (Exception e) {
183             PlatformDependent.throwException(e);
184         }
185 
186         threadLocalMap.setIndexedVariable(index, v);
187         addToVariablesToRemove(threadLocalMap, this);
188         return v;
189     }
190 
191     /**
192      * Set the value for the current thread.
193      */
194     public final void set(V value) {
195         getAndSet(value);
196     }
197 
198     /**
199      * Set the value for the specified thread local map. The specified thread local map must be for the current thread.
200      */
201     public final void set(InternalThreadLocalMap threadLocalMap, V value) {
202         getAndSet(threadLocalMap, value);
203     }
204 
205     /**
206      * Set the value for the current thread and returns the old value.
207      */
208     public V getAndSet(V value) {
209         if (value != InternalThreadLocalMap.UNSET) {
210             InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
211             return setKnownNotUnset(threadLocalMap, value);
212         }
213         return removeAndGet(InternalThreadLocalMap.getIfSet());
214     }
215 
216     /**
217      * Set the value for the specified thread local map. The specified thread local map must be for the current thread.
218      */
219     public V getAndSet(InternalThreadLocalMap threadLocalMap, V value) {
220         if (value != InternalThreadLocalMap.UNSET) {
221             return setKnownNotUnset(threadLocalMap, value);
222         }
223         return removeAndGet(threadLocalMap);
224     }
225 
226     /**
227      * @see InternalThreadLocalMap#setIndexedVariable(int, Object).
228      */
229     @SuppressWarnings("unchecked")
230     private V setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
231         V old = (V) threadLocalMap.getAndSetIndexedVariable(index, value);
232         if (old == UNSET) {
233             addToVariablesToRemove(threadLocalMap, this);
234             return null;
235         }
236         return old;
237     }
238 
239     /**
240      * Returns {@code true} if and only if this thread-local variable is set.
241      */
242     public final boolean isSet() {
243         return isSet(InternalThreadLocalMap.getIfSet());
244     }
245 
246     /**
247      * Returns {@code true} if and only if this thread-local variable is set.
248      * The specified thread local map must be for the current thread.
249      */
250     public final boolean isSet(InternalThreadLocalMap threadLocalMap) {
251         return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index);
252     }
253     /**
254      * Sets the value to uninitialized for the specified thread local map and returns the old value.
255      * After this, any subsequent call to get() will trigger a new call to initialValue().
256      */
257     public final void remove() {
258         remove(InternalThreadLocalMap.getIfSet());
259     }
260 
261     /**
262      * Sets the value to uninitialized for the specified thread local map.
263      * After this, any subsequent call to get() will trigger a new call to initialValue().
264      * The specified thread local map must be for the current thread.
265      */
266     @SuppressWarnings("unchecked")
267     public final void remove(InternalThreadLocalMap threadLocalMap) {
268         removeAndGet(threadLocalMap);
269     }
270 
271     /**
272      * Sets the value to uninitialized for the specified thread local map.
273      * After this, any subsequent call to get() will trigger a new call to initialValue().
274      * The specified thread local map must be for the current thread.
275      */
276     @SuppressWarnings("unchecked")
277     private V removeAndGet(InternalThreadLocalMap threadLocalMap) {
278         if (threadLocalMap == null) {
279             return null;
280         }
281 
282         Object v = threadLocalMap.removeIndexedVariable(index);
283         if (v != InternalThreadLocalMap.UNSET) {
284             removeFromVariablesToRemove(threadLocalMap, this);
285             try {
286                 onRemoval((V) v);
287             } catch (Exception e) {
288                 PlatformDependent.throwException(e);
289             }
290             return (V) v;
291         }
292         return null;
293     }
294 
295     /**
296      * Returns the initial value for this thread-local variable.
297      */
298     protected V initialValue() throws Exception {
299         return null;
300     }
301 
302     /**
303      * Invoked when this thread local variable is removed by {@link #remove()}. Be aware that {@link #remove()}
304      * is not guaranteed to be called when the `Thread` completes which means you can not depend on this for
305      * cleanup of the resources in the case of `Thread` completion.
306      */
307     protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }
308 }