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