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  
17  package io.netty.util.internal;
18  
19  import io.netty.util.concurrent.FastThreadLocal;
20  import io.netty.util.concurrent.FastThreadLocalThread;
21  import io.netty.util.internal.logging.InternalLogger;
22  import io.netty.util.internal.logging.InternalLoggerFactory;
23  
24  import java.nio.charset.Charset;
25  import java.nio.charset.CharsetDecoder;
26  import java.nio.charset.CharsetEncoder;
27  import java.util.ArrayList;
28  import java.util.Arrays;
29  import java.util.BitSet;
30  import java.util.IdentityHashMap;
31  import java.util.Map;
32  import java.util.WeakHashMap;
33  import java.util.concurrent.atomic.AtomicInteger;
34  
35  /**
36   * The internal data structure that stores the thread-local variables for Netty and all {@link FastThreadLocal}s.
37   * Note that this class is for internal use only and is subject to change at any time.  Use {@link FastThreadLocal}
38   * unless you know what you are doing.
39   */
40  public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap {
41  
42      private static final InternalLogger logger = InternalLoggerFactory.getInstance(InternalThreadLocalMap.class);
43      private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
44              new ThreadLocal<InternalThreadLocalMap>();
45      private static final AtomicInteger nextIndex = new AtomicInteger();
46  
47      private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8;
48      private static final int ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD = 1 << 30;
49      // Reference: https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java#l229
50      private static final int ARRAY_LIST_CAPACITY_MAX_SIZE = Integer.MAX_VALUE - 8;
51      private static final int STRING_BUILDER_INITIAL_SIZE;
52      private static final int STRING_BUILDER_MAX_SIZE;
53      private static final int HANDLER_SHARABLE_CACHE_INITIAL_CAPACITY = 4;
54      private static final int INDEXED_VARIABLE_TABLE_INITIAL_SIZE = 32;
55  
56      public static final Object UNSET = new Object();
57  
58      /** Used by {@link FastThreadLocal} */
59      private Object[] indexedVariables;
60  
61      // Core thread-locals
62      private int futureListenerStackDepth;
63      private int localChannelReaderStackDepth;
64      private Map<Class<?>, Boolean> handlerSharableCache;
65      private IntegerHolder counterHashCode;
66      private ThreadLocalRandom random;
67      private Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache;
68      private Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache;
69  
70      // String-related thread-locals
71      private StringBuilder stringBuilder;
72      private Map<Charset, CharsetEncoder> charsetEncoderCache;
73      private Map<Charset, CharsetDecoder> charsetDecoderCache;
74  
75      // ArrayList-related thread-locals
76      private ArrayList<Object> arrayList;
77  
78      private BitSet cleanerFlags;
79  
80      /** @deprecated These padding fields will be removed in the future. */
81      public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8;
82  
83      static {
84          STRING_BUILDER_INITIAL_SIZE =
85                  SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.initialSize", 1024);
86          logger.debug("-Dio.netty.threadLocalMap.stringBuilder.initialSize: {}", STRING_BUILDER_INITIAL_SIZE);
87  
88          STRING_BUILDER_MAX_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.maxSize", 1024 * 4);
89          logger.debug("-Dio.netty.threadLocalMap.stringBuilder.maxSize: {}", STRING_BUILDER_MAX_SIZE);
90      }
91  
92      public static InternalThreadLocalMap getIfSet() {
93          Thread thread = Thread.currentThread();
94          if (thread instanceof FastThreadLocalThread) {
95              return ((FastThreadLocalThread) thread).threadLocalMap();
96          }
97          return slowThreadLocalMap.get();
98      }
99  
100     public static InternalThreadLocalMap get() {
101         Thread thread = Thread.currentThread();
102         if (thread instanceof FastThreadLocalThread) {
103             return fastGet((FastThreadLocalThread) thread);
104         } else {
105             return slowGet();
106         }
107     }
108 
109     private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
110         InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
111         if (threadLocalMap == null) {
112             thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
113         }
114         return threadLocalMap;
115     }
116 
117     private static InternalThreadLocalMap slowGet() {
118         InternalThreadLocalMap ret = slowThreadLocalMap.get();
119         if (ret == null) {
120             ret = new InternalThreadLocalMap();
121             slowThreadLocalMap.set(ret);
122         }
123         return ret;
124     }
125 
126     public static void remove() {
127         Thread thread = Thread.currentThread();
128         if (thread instanceof FastThreadLocalThread) {
129             ((FastThreadLocalThread) thread).setThreadLocalMap(null);
130         } else {
131             slowThreadLocalMap.remove();
132         }
133     }
134 
135     public static void destroy() {
136         slowThreadLocalMap.remove();
137     }
138 
139     public static int nextVariableIndex() {
140         int index = nextIndex.getAndIncrement();
141         if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
142             nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
143             throw new IllegalStateException("too many thread-local indexed variables");
144         }
145         return index;
146     }
147 
148     public static int lastVariableIndex() {
149         return nextIndex.get() - 1;
150     }
151 
152     private InternalThreadLocalMap() {
153         indexedVariables = newIndexedVariableTable();
154     }
155 
156     private static Object[] newIndexedVariableTable() {
157         Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
158         Arrays.fill(array, UNSET);
159         return array;
160     }
161 
162     public int size() {
163         int count = 0;
164 
165         if (futureListenerStackDepth != 0) {
166             count ++;
167         }
168         if (localChannelReaderStackDepth != 0) {
169             count ++;
170         }
171         if (handlerSharableCache != null) {
172             count ++;
173         }
174         if (counterHashCode != null) {
175             count ++;
176         }
177         if (random != null) {
178             count ++;
179         }
180         if (typeParameterMatcherGetCache != null) {
181             count ++;
182         }
183         if (typeParameterMatcherFindCache != null) {
184             count ++;
185         }
186         if (stringBuilder != null) {
187             count ++;
188         }
189         if (charsetEncoderCache != null) {
190             count ++;
191         }
192         if (charsetDecoderCache != null) {
193             count ++;
194         }
195         if (arrayList != null) {
196             count ++;
197         }
198 
199         for (Object o: indexedVariables) {
200             if (o != UNSET) {
201                 count ++;
202             }
203         }
204 
205         // We should subtract 1 from the count because the first element in 'indexedVariables' is reserved
206         // by 'FastThreadLocal' to keep the list of 'FastThreadLocal's to remove on 'FastThreadLocal.removeAll()'.
207         return count - 1;
208     }
209 
210     public StringBuilder stringBuilder() {
211         StringBuilder sb = stringBuilder;
212         if (sb == null) {
213             return stringBuilder = new StringBuilder(STRING_BUILDER_INITIAL_SIZE);
214         }
215         if (sb.capacity() > STRING_BUILDER_MAX_SIZE) {
216             sb.setLength(STRING_BUILDER_INITIAL_SIZE);
217             sb.trimToSize();
218         }
219         sb.setLength(0);
220         return sb;
221     }
222 
223     public Map<Charset, CharsetEncoder> charsetEncoderCache() {
224         Map<Charset, CharsetEncoder> cache = charsetEncoderCache;
225         if (cache == null) {
226             charsetEncoderCache = cache = new IdentityHashMap<Charset, CharsetEncoder>();
227         }
228         return cache;
229     }
230 
231     public Map<Charset, CharsetDecoder> charsetDecoderCache() {
232         Map<Charset, CharsetDecoder> cache = charsetDecoderCache;
233         if (cache == null) {
234             charsetDecoderCache = cache = new IdentityHashMap<Charset, CharsetDecoder>();
235         }
236         return cache;
237     }
238 
239     public <E> ArrayList<E> arrayList() {
240         return arrayList(DEFAULT_ARRAY_LIST_INITIAL_CAPACITY);
241     }
242 
243     @SuppressWarnings("unchecked")
244     public <E> ArrayList<E> arrayList(int minCapacity) {
245         ArrayList<E> list = (ArrayList<E>) arrayList;
246         if (list == null) {
247             arrayList = new ArrayList<Object>(minCapacity);
248             return (ArrayList<E>) arrayList;
249         }
250         list.clear();
251         list.ensureCapacity(minCapacity);
252         return list;
253     }
254 
255     public int futureListenerStackDepth() {
256         return futureListenerStackDepth;
257     }
258 
259     public void setFutureListenerStackDepth(int futureListenerStackDepth) {
260         this.futureListenerStackDepth = futureListenerStackDepth;
261     }
262 
263     public ThreadLocalRandom random() {
264         ThreadLocalRandom r = random;
265         if (r == null) {
266             random = r = new ThreadLocalRandom();
267         }
268         return r;
269     }
270 
271     public Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache() {
272         Map<Class<?>, TypeParameterMatcher> cache = typeParameterMatcherGetCache;
273         if (cache == null) {
274             typeParameterMatcherGetCache = cache = new IdentityHashMap<Class<?>, TypeParameterMatcher>();
275         }
276         return cache;
277     }
278 
279     public Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache() {
280         Map<Class<?>, Map<String, TypeParameterMatcher>> cache = typeParameterMatcherFindCache;
281         if (cache == null) {
282             typeParameterMatcherFindCache = cache = new IdentityHashMap<Class<?>, Map<String, TypeParameterMatcher>>();
283         }
284         return cache;
285     }
286 
287     @Deprecated
288     public IntegerHolder counterHashCode() {
289         return counterHashCode;
290     }
291 
292     @Deprecated
293     public void setCounterHashCode(IntegerHolder counterHashCode) {
294         this.counterHashCode = counterHashCode;
295     }
296 
297     public Map<Class<?>, Boolean> handlerSharableCache() {
298         Map<Class<?>, Boolean> cache = handlerSharableCache;
299         if (cache == null) {
300             // Start with small capacity to keep memory overhead as low as possible.
301             handlerSharableCache = cache = new WeakHashMap<Class<?>, Boolean>(HANDLER_SHARABLE_CACHE_INITIAL_CAPACITY);
302         }
303         return cache;
304     }
305 
306     public int localChannelReaderStackDepth() {
307         return localChannelReaderStackDepth;
308     }
309 
310     public void setLocalChannelReaderStackDepth(int localChannelReaderStackDepth) {
311         this.localChannelReaderStackDepth = localChannelReaderStackDepth;
312     }
313 
314     public Object indexedVariable(int index) {
315         Object[] lookup = indexedVariables;
316         return index < lookup.length? lookup[index] : UNSET;
317     }
318 
319     /**
320      * @return {@code true} if and only if a new thread-local variable has been created
321      */
322     public boolean setIndexedVariable(int index, Object value) {
323         Object[] lookup = indexedVariables;
324         if (index < lookup.length) {
325             Object oldValue = lookup[index];
326             lookup[index] = value;
327             return oldValue == UNSET;
328         } else {
329             expandIndexedVariableTableAndSet(index, value);
330             return true;
331         }
332     }
333 
334     private void expandIndexedVariableTableAndSet(int index, Object value) {
335         Object[] oldArray = indexedVariables;
336         final int oldCapacity = oldArray.length;
337         int newCapacity;
338         if (index < ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD) {
339             newCapacity = index;
340             newCapacity |= newCapacity >>>  1;
341             newCapacity |= newCapacity >>>  2;
342             newCapacity |= newCapacity >>>  4;
343             newCapacity |= newCapacity >>>  8;
344             newCapacity |= newCapacity >>> 16;
345             newCapacity ++;
346         } else {
347             newCapacity = ARRAY_LIST_CAPACITY_MAX_SIZE;
348         }
349 
350         Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
351         Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
352         newArray[index] = value;
353         indexedVariables = newArray;
354     }
355 
356     public Object removeIndexedVariable(int index) {
357         Object[] lookup = indexedVariables;
358         if (index < lookup.length) {
359             Object v = lookup[index];
360             lookup[index] = UNSET;
361             return v;
362         } else {
363             return UNSET;
364         }
365     }
366 
367     public boolean isIndexedVariableSet(int index) {
368         Object[] lookup = indexedVariables;
369         return index < lookup.length && lookup[index] != UNSET;
370     }
371 
372     public boolean isCleanerFlagSet(int index) {
373         return cleanerFlags != null && cleanerFlags.get(index);
374     }
375 
376     public void setCleanerFlag(int index) {
377         if (cleanerFlags == null) {
378             cleanerFlags = new BitSet();
379         }
380         cleanerFlags.set(index);
381     }
382 }