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         return initialize(threadLocalMap);
143     }
144
145     /**
146      * Returns the current value for the current thread if it exists, {@code null} otherwise.
147      */

148     @SuppressWarnings("unchecked")
149     public final V getIfExists() {
150         InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
151         if (threadLocalMap != null) {
152             Object v = threadLocalMap.indexedVariable(index);
153             if (v != InternalThreadLocalMap.UNSET) {
154                 return (V) v;
155             }
156         }
157         return null;
158     }
159
160     /**
161      * Returns the current value for the specified thread local map.
162      * The specified thread local map must be for the current thread.
163      */

164     @SuppressWarnings("unchecked")
165     public final V get(InternalThreadLocalMap threadLocalMap) {
166         Object v = threadLocalMap.indexedVariable(index);
167         if (v != InternalThreadLocalMap.UNSET) {
168             return (V) v;
169         }
170
171         return initialize(threadLocalMap);
172     }
173
174     private V initialize(InternalThreadLocalMap threadLocalMap) {
175         V v = null;
176         try {
177             v = initialValue();
178         } catch (Exception e) {
179             PlatformDependent.throwException(e);
180         }
181
182         threadLocalMap.setIndexedVariable(index, v);
183         addToVariablesToRemove(threadLocalMap, this);
184         return v;
185     }
186
187     /**
188      * Set the value for the current thread.
189      */

190     public final void set(V value) {
191         if (value != InternalThreadLocalMap.UNSET) {
192             InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
193             setKnownNotUnset(threadLocalMap, value);
194         } else {
195             remove();
196         }
197     }
198
199     /**
200      * Set the value for the specified thread local map. The specified thread local map must be for the current thread.
201      */

202     public final void set(InternalThreadLocalMap threadLocalMap, V value) {
203         if (value != InternalThreadLocalMap.UNSET) {
204             setKnownNotUnset(threadLocalMap, value);
205         } else {
206             remove(threadLocalMap);
207         }
208     }
209
210     /**
211      * @return see {@link InternalThreadLocalMap#setIndexedVariable(int, Object)}.
212      */

213     private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
214         if (threadLocalMap.setIndexedVariable(index, value)) {
215             addToVariablesToRemove(threadLocalMap, this);
216         }
217     }
218
219     /**
220      * Returns {@code trueif and only if this thread-local variable is set.
221      */

222     public final boolean isSet() {
223         return isSet(InternalThreadLocalMap.getIfSet());
224     }
225
226     /**
227      * Returns {@code trueif and only if this thread-local variable is set.
228      * The specified thread local map must be for the current thread.
229      */

230     public final boolean isSet(InternalThreadLocalMap threadLocalMap) {
231         return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index);
232     }
233     /**
234      * Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue().
235      */

236     public final void remove() {
237         remove(InternalThreadLocalMap.getIfSet());
238     }
239
240     /**
241      * Sets the value to uninitialized for the specified thread local map;
242      * a proceeding call to get() will trigger a call to initialValue().
243      * The specified thread local map must be for the current thread.
244      */

245     @SuppressWarnings("unchecked")
246     public final void remove(InternalThreadLocalMap threadLocalMap) {
247         if (threadLocalMap == null) {
248             return;
249         }
250
251         Object v = threadLocalMap.removeIndexedVariable(index);
252         removeFromVariablesToRemove(threadLocalMap, this);
253
254         if (v != InternalThreadLocalMap.UNSET) {
255             try {
256                 onRemoval((V) v);
257             } catch (Exception e) {
258                 PlatformDependent.throwException(e);
259             }
260         }
261     }
262
263     /**
264      * Returns the initial value for this thread-local variable.
265      */

266     protected V initialValue() throws Exception {
267         return null;
268     }
269
270     /**
271      * Invoked when this thread local variable is removed by {@link #remove()}. Be aware that {@link #remove()}
272      * is not guaranteed to be called when the `Thread` completes which means you can not depend on this for
273      * cleanup of the resources in the case of `Thread` completion.
274      */

275     protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }
276 }
277