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.ObjectCleaner;
20  import io.netty.util.internal.PlatformDependent;
21  
22  import java.util.Collections;
23  import java.util.IdentityHashMap;
24  import java.util.Set;
25  
26  /**
27   * A special variant of {@link ThreadLocal} that yields higher access performance when accessed from a
28   * {@link FastThreadLocalThread}.
29   * <p>
30   * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table,
31   * to look for a variable.  Although seemingly very subtle, it yields slight performance advantage over using a hash
32   * table, and it is useful when accessed frequently.
33   * </p><p>
34   * To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype.
35   * By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason.
36   * </p><p>
37   * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires
38   * a special field to store the necessary state.  An access by any other kind of thread falls back to a regular
39   * {@link ThreadLocal}.
40   * </p>
41   *
42   * @param <V> the type of the thread-local variable
43   * @see ThreadLocal
44   */
45  public class FastThreadLocal<V> {
46  
47      private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
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(variablesToRemoveIndex);
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[variablesToRemove.size()]);
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(variablesToRemoveIndex);
101         Set<FastThreadLocal<?>> variablesToRemove;
102         if (v == InternalThreadLocalMap.UNSET || v == null) {
103             variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
104             threadLocalMap.setIndexedVariable(variablesToRemoveIndex, 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(variablesToRemoveIndex);
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     private final int cleanerFlagIndex;
129 
130     public FastThreadLocal() {
131         index = InternalThreadLocalMap.nextVariableIndex();
132         cleanerFlagIndex = InternalThreadLocalMap.nextVariableIndex();
133     }
134 
135     /**
136      * Returns the current value for the current thread
137      */
138     @SuppressWarnings("unchecked")
139     public final V get() {
140         InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
141         Object v = threadLocalMap.indexedVariable(index);
142         if (v != InternalThreadLocalMap.UNSET) {
143             return (V) v;
144         }
145 
146         V value = initialize(threadLocalMap);
147         registerCleaner(threadLocalMap);
148         return value;
149     }
150 
151     private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
152         Thread current = Thread.currentThread();
153         if (FastThreadLocalThread.willCleanupFastThreadLocals(current) ||
154             threadLocalMap.indexedVariable(cleanerFlagIndex) != InternalThreadLocalMap.UNSET) {
155             return;
156         }
157         // removeIndexedVariable(cleanerFlagIndex) isn't necessary because the finally cleanup is tied to the lifetime
158         // of the thread, and this Object will be discarded if the associated thread is GCed.
159         threadLocalMap.setIndexedVariable(cleanerFlagIndex, Boolean.TRUE);
160 
161         // We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released
162         // and FastThreadLocal.onRemoval(...) will be called.
163         ObjectCleaner.register(current, new Runnable() {
164             @Override
165             public void run() {
166                 remove(threadLocalMap);
167 
168                 // It's fine to not call InternalThreadLocalMap.remove() here as this will only be triggered once
169                 // the Thread is collected by GC. In this case the ThreadLocal will be gone away already.
170             }
171         });
172     }
173 
174     /**
175      * Returns the current value for the specified thread local map.
176      * The specified thread local map must be for the current thread.
177      */
178     @SuppressWarnings("unchecked")
179     public final V get(InternalThreadLocalMap threadLocalMap) {
180         Object v = threadLocalMap.indexedVariable(index);
181         if (v != InternalThreadLocalMap.UNSET) {
182             return (V) v;
183         }
184 
185         return initialize(threadLocalMap);
186     }
187 
188     private V initialize(InternalThreadLocalMap threadLocalMap) {
189         V v = null;
190         try {
191             v = initialValue();
192         } catch (Exception e) {
193             PlatformDependent.throwException(e);
194         }
195 
196         threadLocalMap.setIndexedVariable(index, v);
197         addToVariablesToRemove(threadLocalMap, this);
198         return v;
199     }
200 
201     /**
202      * Set the value for the current thread.
203      */
204     public final void set(V value) {
205         if (value != InternalThreadLocalMap.UNSET) {
206             InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
207             if (setKnownNotUnset(threadLocalMap, value)) {
208                 registerCleaner(threadLocalMap);
209             }
210         } else {
211             remove();
212         }
213     }
214 
215     /**
216      * Set the value for the specified thread local map. The specified thread local map must be for the current thread.
217      */
218     public final void set(InternalThreadLocalMap threadLocalMap, V value) {
219         if (value != InternalThreadLocalMap.UNSET) {
220             setKnownNotUnset(threadLocalMap, value);
221         } else {
222             remove(threadLocalMap);
223         }
224     }
225 
226     /**
227      * @return see {@link InternalThreadLocalMap#setIndexedVariable(int, Object)}.
228      */
229     private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
230         if (threadLocalMap.setIndexedVariable(index, value)) {
231             addToVariablesToRemove(threadLocalMap, this);
232             return true;
233         }
234         return false;
235     }
236 
237     /**
238      * Returns {@code true} if and only if this thread-local variable is set.
239      */
240     public final boolean isSet() {
241         return isSet(InternalThreadLocalMap.getIfSet());
242     }
243 
244     /**
245      * Returns {@code true} if and only if this thread-local variable is set.
246      * The specified thread local map must be for the current thread.
247      */
248     public final boolean isSet(InternalThreadLocalMap threadLocalMap) {
249         return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index);
250     }
251     /**
252      * Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue().
253      */
254     public final void remove() {
255         remove(InternalThreadLocalMap.getIfSet());
256     }
257 
258     /**
259      * Sets the value to uninitialized for the specified thread local map;
260      * a proceeding call to get() will trigger a call to initialValue().
261      * The specified thread local map must be for the current thread.
262      */
263     @SuppressWarnings("unchecked")
264     public final void remove(InternalThreadLocalMap threadLocalMap) {
265         if (threadLocalMap == null) {
266             return;
267         }
268 
269         Object v = threadLocalMap.removeIndexedVariable(index);
270         removeFromVariablesToRemove(threadLocalMap, this);
271 
272         if (v != InternalThreadLocalMap.UNSET) {
273             try {
274                 onRemoval((V) v);
275             } catch (Exception e) {
276                 PlatformDependent.throwException(e);
277             }
278         }
279     }
280 
281     /**
282      * Returns the initial value for this thread-local variable.
283      */
284     protected V initialValue() throws Exception {
285         return null;
286     }
287 
288     /**
289      * Invoked when this thread local variable is removed by {@link #remove()}.
290      */
291     protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }
292 }