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