/* * Copyright 2014 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.util.internal; import io.netty.util.concurrent.FastThreadLocal; import io.netty.util.concurrent.FastThreadLocalThread; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CharsetEncoder; import java.util.Arrays; import java.util.IdentityHashMap; import java.util.Map; import java.util.WeakHashMap; /** * The internal data structure that stores the thread-local variables for Netty and all {@link FastThreadLocal}s. * Note that this class is for internal use only and is subject to change at any time. Use {@link FastThreadLocal} * unless you know what you are doing. */ public final class InternalThreadLocalMap extends UnpaddedInternalThreadLocalMap { public static final Object UNSET = new Object(); public static InternalThreadLocalMap getIfSet() { Thread thread = Thread.currentThread(); InternalThreadLocalMap threadLocalMap; if (thread instanceof FastThreadLocalThread) { threadLocalMap = ((FastThreadLocalThread) thread).threadLocalMap(); } else { ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; if (slowThreadLocalMap == null) { threadLocalMap = null; } else { threadLocalMap = slowThreadLocalMap.get(); } } return threadLocalMap; } public static InternalThreadLocalMap get() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return fastGet((FastThreadLocalThread) thread); } else { return slowGet(); } } private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) { InternalThreadLocalMap threadLocalMap = thread.threadLocalMap(); if (threadLocalMap == null) { thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap()); } return threadLocalMap; } private static InternalThreadLocalMap slowGet() { ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; if (slowThreadLocalMap == null) { UnpaddedInternalThreadLocalMap.slowThreadLocalMap = slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>(); } InternalThreadLocalMap ret = slowThreadLocalMap.get(); if (ret == null) { ret = new InternalThreadLocalMap(); slowThreadLocalMap.set(ret); } return ret; } public static void remove() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { ((FastThreadLocalThread) thread).setThreadLocalMap(null); } else { ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap; if (slowThreadLocalMap != null) { slowThreadLocalMap.remove(); } } } public static void destroy() { slowThreadLocalMap = null; } public static int nextVariableIndex() { int index = nextIndex.getAndIncrement(); if (index < 0) { nextIndex.decrementAndGet(); throw new IllegalStateException("too many thread-local indexed variables"); } return index; } public static int lastVariableIndex() { return nextIndex.get() - 1; } // Cache line padding (must be public) // With CompressedOops enabled, an instance of this class should occupy at least 128 bytes. public long rp1, rp2, rp3, rp4, rp5, rp6, rp7, rp8, rp9; private InternalThreadLocalMap() { super(newIndexedVariableTable()); } private static Object[] newIndexedVariableTable() { Object[] array = new Object[32]; Arrays.fill(array, UNSET); return array; } public int size() { int count = 0; if (futureListenerStackDepth != 0) { count ++; } if (localChannelReaderStackDepth != 0) { count ++; } if (handlerSharableCache != null) { count ++; } if (counterHashCode != null) { count ++; } if (random != null) { count ++; } if (typeParameterMatcherGetCache != null) { count ++; } if (typeParameterMatcherFindCache != null) { count ++; } if (stringBuilder != null) { count ++; } if (charsetEncoderCache != null) { count ++; } if (charsetDecoderCache != null) { count ++; } for (Object o: indexedVariables) { if (o != UNSET) { count ++; } } // We should subtract 1 from the count because the first element in 'indexedVariables' is reserved // by 'FastThreadLocal' to keep the list of 'FastThreadLocal's to remove on 'FastThreadLocal.removeAll()'. return count - 1; } public StringBuilder stringBuilder() { StringBuilder builder = stringBuilder; if (builder == null) { stringBuilder = builder = new StringBuilder(512); } else { builder.setLength(0); } return builder; } public Map<Charset, CharsetEncoder> charsetEncoderCache() { Map<Charset, CharsetEncoder> cache = charsetEncoderCache; if (cache == null) { charsetEncoderCache = cache = new IdentityHashMap<Charset, CharsetEncoder>(); } return cache; } public Map<Charset, CharsetDecoder> charsetDecoderCache() { Map<Charset, CharsetDecoder> cache = charsetDecoderCache; if (cache == null) { charsetDecoderCache = cache = new IdentityHashMap<Charset, CharsetDecoder>(); } return cache; } public int futureListenerStackDepth() { return futureListenerStackDepth; } public void setFutureListenerStackDepth(int futureListenerStackDepth) { this.futureListenerStackDepth = futureListenerStackDepth; } public ThreadLocalRandom random() { ThreadLocalRandom r = random; if (r == null) { random = r = new ThreadLocalRandom(); } return r; } public Map<Class<?>, TypeParameterMatcher> typeParameterMatcherGetCache() { Map<Class<?>, TypeParameterMatcher> cache = typeParameterMatcherGetCache; if (cache == null) { typeParameterMatcherGetCache = cache = new IdentityHashMap<Class<?>, TypeParameterMatcher>(); } return cache; } public Map<Class<?>, Map<String, TypeParameterMatcher>> typeParameterMatcherFindCache() { Map<Class<?>, Map<String, TypeParameterMatcher>> cache = typeParameterMatcherFindCache; if (cache == null) { typeParameterMatcherFindCache = cache = new IdentityHashMap<Class<?>, Map<String, TypeParameterMatcher>>(); } return cache; } public IntegerHolder counterHashCode() { return counterHashCode; } public void setCounterHashCode(IntegerHolder counterHashCode) { this.counterHashCode = counterHashCode; } public Map<Class<?>, Boolean> handlerSharableCache() { Map<Class<?>, Boolean> cache = handlerSharableCache; if (cache == null) { // Start with small capacity to keep memory overhead as low as possible. handlerSharableCache = cache = new WeakHashMap<Class<?>, Boolean>(4); } return cache; } public int localChannelReaderStackDepth() { return localChannelReaderStackDepth; } public void setLocalChannelReaderStackDepth(int localChannelReaderStackDepth) { this.localChannelReaderStackDepth = localChannelReaderStackDepth; } public Object indexedVariable(int index) { Object[] lookup = indexedVariables; return index < lookup.length? lookup[index] : UNSET; } /** * @return {@code true} if and only if a new thread-local variable has been created */ public boolean setIndexedVariable(int index, Object value) { Object[] lookup = indexedVariables; if (index < lookup.length) { Object oldValue = lookup[index]; lookup[index] = value; return oldValue == UNSET; } else { expandIndexedVariableTableAndSet(index, value); return true; } } private void expandIndexedVariableTableAndSet(int index, Object value) { Object[] oldArray = indexedVariables; final int oldCapacity = oldArray.length; int newCapacity = index; newCapacity |= newCapacity >>> 1; newCapacity |= newCapacity >>> 2; newCapacity |= newCapacity >>> 4; newCapacity |= newCapacity >>> 8; newCapacity |= newCapacity >>> 16; newCapacity ++; Object[] newArray = Arrays.copyOf(oldArray, newCapacity); Arrays.fill(newArray, oldCapacity, newArray.length, UNSET); newArray[index] = value; indexedVariables = newArray; } public Object removeIndexedVariable(int index) { Object[] lookup = indexedVariables; if (index < lookup.length) { Object v = lookup[index]; lookup[index] = UNSET; return v; } else { return UNSET; } } public boolean isIndexedVariableSet(int index) { Object[] lookup = indexedVariables; return index < lookup.length && lookup[index] != UNSET; } }