/*
 * 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 io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
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 FastThreadLocals. Note that this class is for internal use only and is subject to change at any time. Use FastThreadLocal unless you know what you are doing.
/** * 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 { private static final InternalLogger logger = InternalLoggerFactory.getInstance(InternalThreadLocalMap.class); private static final int DEFAULT_ARRAY_LIST_INITIAL_CAPACITY = 8; private static final int STRING_BUILDER_INITIAL_SIZE; private static final int STRING_BUILDER_MAX_SIZE; public static final Object UNSET = new Object(); private BitSet cleanerFlags; static { STRING_BUILDER_INITIAL_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.initialSize", 1024); logger.debug("-Dio.netty.threadLocalMap.stringBuilder.initialSize: {}", STRING_BUILDER_INITIAL_SIZE); STRING_BUILDER_MAX_SIZE = SystemPropertyUtil.getInt("io.netty.threadLocalMap.stringBuilder.maxSize", 1024 * 4); logger.debug("-Dio.netty.threadLocalMap.stringBuilder.maxSize: {}", STRING_BUILDER_MAX_SIZE); } public static InternalThreadLocalMap getIfSet() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return ((FastThreadLocalThread) thread).threadLocalMap(); } return slowThreadLocalMap.get(); } 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; 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 { slowThreadLocalMap.remove(); } } public static void destroy() { slowThreadLocalMap.remove(); } 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 ++; } if (arrayList != 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 sb = stringBuilder; if (sb == null) { return stringBuilder = new StringBuilder(STRING_BUILDER_INITIAL_SIZE); } if (sb.capacity() > STRING_BUILDER_MAX_SIZE) { sb.setLength(STRING_BUILDER_INITIAL_SIZE); sb.trimToSize(); } sb.setLength(0); return sb; } 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 <E> ArrayList<E> arrayList() { return arrayList(DEFAULT_ARRAY_LIST_INITIAL_CAPACITY); } @SuppressWarnings("unchecked") public <E> ArrayList<E> arrayList(int minCapacity) { ArrayList<E> list = (ArrayList<E>) arrayList; if (list == null) { arrayList = new ArrayList<Object>(minCapacity); return (ArrayList<E>) arrayList; } list.clear(); list.ensureCapacity(minCapacity); return list; } 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; }
Returns:true if and only if a new thread-local variable has been created
/** * @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; } public boolean isCleanerFlagSet(int index) { return cleanerFlags != null && cleanerFlags.get(index); } public void setCleanerFlag(int index) { if (cleanerFlags == null) { cleanerFlags = new BitSet(); } cleanerFlags.set(index); } }