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.netty5.util.concurrent;
18  
19  import java.util.Arrays;
20  import java.util.BitSet;
21  import java.util.concurrent.atomic.AtomicInteger;
22  
23  /**
24   * The internal data structure that stores the thread-local variables for Netty and all {@link FastThreadLocal}s.
25   * Note that this class is for internal use only and is subject to change at any time.  Use {@link FastThreadLocal}
26   * unless you know what you are doing.
27   */
28  final class InternalThreadLocalMap {
29  
30      private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap =
31              new ThreadLocal<>();
32      private static final AtomicInteger nextIndex = new AtomicInteger();
33  
34      private static final int ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD = 1 << 30;
35      // Reference: https://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/util/ArrayList.java#l229
36      private static final int ARRAY_LIST_CAPACITY_MAX_SIZE = Integer.MAX_VALUE - 8;
37      private static final int INDEXED_VARIABLE_TABLE_INITIAL_SIZE = 32;
38  
39      static final Object UNSET = new Object();
40  
41      /** Used by {@link FastThreadLocal} */
42      private Object[] indexedVariables;
43  
44      private BitSet cleanerFlags;
45  
46      static InternalThreadLocalMap getIfSet() {
47          Thread thread = Thread.currentThread();
48          if (thread instanceof FastThreadLocalThread) {
49              return ((FastThreadLocalThread) thread).threadLocalMap();
50          }
51          return slowThreadLocalMap.get();
52      }
53  
54      static InternalThreadLocalMap get() {
55          Thread thread = Thread.currentThread();
56          if (thread instanceof FastThreadLocalThread) {
57              return fastGet((FastThreadLocalThread) thread);
58          } else {
59              return slowGet();
60          }
61      }
62  
63      private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
64          InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
65          if (threadLocalMap == null) {
66              thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
67          }
68          return threadLocalMap;
69      }
70  
71      private static InternalThreadLocalMap slowGet() {
72          InternalThreadLocalMap ret = slowThreadLocalMap.get();
73          if (ret == null) {
74              ret = new InternalThreadLocalMap();
75              slowThreadLocalMap.set(ret);
76          }
77          return ret;
78      }
79  
80      static void remove() {
81          Thread thread = Thread.currentThread();
82          if (thread instanceof FastThreadLocalThread) {
83              ((FastThreadLocalThread) thread).setThreadLocalMap(null);
84          } else {
85              slowThreadLocalMap.remove();
86          }
87      }
88  
89      static void destroy() {
90          slowThreadLocalMap.remove();
91      }
92  
93      static int nextVariableIndex() {
94          int index = nextIndex.getAndIncrement();
95          if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
96              nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
97              throw new IllegalStateException("too many thread-local indexed variables");
98          }
99          return index;
100     }
101 
102     static int lastVariableIndex() {
103         return nextIndex.get() - 1;
104     }
105 
106     private InternalThreadLocalMap() {
107         indexedVariables = newIndexedVariableTable();
108     }
109 
110     private static Object[] newIndexedVariableTable() {
111         Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
112         Arrays.fill(array, UNSET);
113         return array;
114     }
115 
116     int size() {
117         int count = 0;
118         for (Object o: indexedVariables) {
119             if (o != UNSET) {
120                 count ++;
121             }
122         }
123 
124         // We should subtract 1 from the count because the first element in 'indexedVariables' is reserved
125         // by 'FastThreadLocal' to keep the list of 'FastThreadLocal's to remove on 'FastThreadLocal.removeAll()'.
126         return count - 1;
127     }
128 
129     Object indexedVariable(int index) {
130         Object[] lookup = indexedVariables;
131         return index < lookup.length? lookup[index] : UNSET;
132     }
133 
134     /**
135      * @return {@code true} if and only if a new thread-local variable has been created
136      */
137     boolean setIndexedVariable(int index, Object value) {
138         Object[] lookup = indexedVariables;
139         if (index < lookup.length) {
140             Object oldValue = lookup[index];
141             lookup[index] = value;
142             return oldValue == UNSET;
143         } else {
144             expandIndexedVariableTableAndSet(index, value);
145             return true;
146         }
147     }
148 
149     private void expandIndexedVariableTableAndSet(int index, Object value) {
150         Object[] oldArray = indexedVariables;
151         final int oldCapacity = oldArray.length;
152         int newCapacity;
153         if (index < ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD) {
154             newCapacity = index;
155             newCapacity |= newCapacity >>>  1;
156             newCapacity |= newCapacity >>>  2;
157             newCapacity |= newCapacity >>>  4;
158             newCapacity |= newCapacity >>>  8;
159             newCapacity |= newCapacity >>> 16;
160             newCapacity ++;
161         } else {
162             newCapacity = ARRAY_LIST_CAPACITY_MAX_SIZE;
163         }
164 
165         Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
166         Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
167         newArray[index] = value;
168         indexedVariables = newArray;
169     }
170 
171     Object removeIndexedVariable(int index) {
172         Object[] lookup = indexedVariables;
173         if (index < lookup.length) {
174             Object v = lookup[index];
175             lookup[index] = UNSET;
176             return v;
177         } else {
178             return UNSET;
179         }
180     }
181 
182     boolean isIndexedVariableSet(int index) {
183         Object[] lookup = indexedVariables;
184         return index < lookup.length && lookup[index] != UNSET;
185     }
186 
187     boolean isCleanerFlagSet(int index) {
188         return cleanerFlags != null && cleanerFlags.get(index);
189     }
190 
191     void setCleanerFlag(int index) {
192         if (cleanerFlags == null) {
193             cleanerFlags = new BitSet();
194         }
195         cleanerFlags.set(index);
196     }
197 }