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 true} if 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 true} if 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