/* * 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.concurrent; import io.netty.util.internal.InternalThreadLocalMap; import io.netty.util.internal.PlatformDependent; import java.util.Collections; import java.util.IdentityHashMap; import java.util.Set; /** * A special variant of {@link ThreadLocal} that yields higher access performance when accessed from a * {@link FastThreadLocalThread}. * <p> * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table, * to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash * table, and it is useful when accessed frequently. * </p><p> * To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype. * By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason. * </p><p> * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires * a special field to store the necessary state. An access by any other kind of thread falls back to a regular * {@link ThreadLocal}. * </p> * * @param <V> the type of the thread-local variable * @see ThreadLocal */ public class FastThreadLocal<V> { private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex(); /** * Removes all {@link FastThreadLocal} variables bound to the current thread. This operation is useful when you * are in a container environment, and you don't want to leave the thread local variables in the threads you do not * manage. */ public static void removeAll() { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); if (threadLocalMap == null) { return; } try { Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); if (v != null && v != InternalThreadLocalMap.UNSET) { @SuppressWarnings("unchecked") Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v; FastThreadLocal<?>[] variablesToRemoveArray = variablesToRemove.toArray(new FastThreadLocal[variablesToRemove.size()]); for (FastThreadLocal<?> tlv: variablesToRemoveArray) { tlv.remove(threadLocalMap); } } } finally { InternalThreadLocalMap.remove(); } } /** * Returns the number of thread local variables bound to the current thread. */ public static int size() { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet(); if (threadLocalMap == null) { return 0; } else { return threadLocalMap.size(); } } /** * Destroys the data structure that keeps all {@link FastThreadLocal} variables accessed from * non-{@link FastThreadLocalThread}s. This operation is useful when you are in a container environment, and you * do not want to leave the thread local variables in the threads you do not manage. Call this method when your * application is being unloaded from the container. */ public static void destroy() { InternalThreadLocalMap.destroy(); } @SuppressWarnings("unchecked") private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) { Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); Set<FastThreadLocal<?>> variablesToRemove; if (v == InternalThreadLocalMap.UNSET || v == null) { variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>()); threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove); } else { variablesToRemove = (Set<FastThreadLocal<?>>) v; } variablesToRemove.add(variable); } private static void removeFromVariablesToRemove( InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) { Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); if (v == InternalThreadLocalMap.UNSET || v == null) { return; } @SuppressWarnings("unchecked") Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v; variablesToRemove.remove(variable); } private final int index; public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); } /** * Returns the current value for the current thread */ public final V get() { return get(InternalThreadLocalMap.get()); } /** * Returns the current value for the specified thread local map. * The specified thread local map must be for the current thread. */ @SuppressWarnings("unchecked") public final V get(InternalThreadLocalMap threadLocalMap) { Object v = threadLocalMap.indexedVariable(index); if (v != InternalThreadLocalMap.UNSET) { return (V) v; } return initialize(threadLocalMap); } private V initialize(InternalThreadLocalMap threadLocalMap) { V v = null; try { v = initialValue(); } catch (Exception e) { PlatformDependent.throwException(e); } threadLocalMap.setIndexedVariable(index, v); addToVariablesToRemove(threadLocalMap, this); return v; } /** * Set the value for the current thread. */ public final void set(V value) { if (value != InternalThreadLocalMap.UNSET) { set(InternalThreadLocalMap.get(), value); } else { remove(); } } /** * Set the value for the specified thread local map. The specified thread local map must be for the current thread. */ public final void set(InternalThreadLocalMap threadLocalMap, V value) { if (value != InternalThreadLocalMap.UNSET) { if (threadLocalMap.setIndexedVariable(index, value)) { addToVariablesToRemove(threadLocalMap, this); } } else { remove(threadLocalMap); } } /** * Returns {@code true} if and only if this thread-local variable is set. */ public final boolean isSet() { return isSet(InternalThreadLocalMap.getIfSet()); } /** * Returns {@code true} if and only if this thread-local variable is set. * The specified thread local map must be for the current thread. */ public final boolean isSet(InternalThreadLocalMap threadLocalMap) { return threadLocalMap != null && threadLocalMap.isIndexedVariableSet(index); } /** * Sets the value to uninitialized; a proceeding call to get() will trigger a call to initialValue(). */ public final void remove() { remove(InternalThreadLocalMap.getIfSet()); } /** * Sets the value to uninitialized for the specified thread local map; * a proceeding call to get() will trigger a call to initialValue(). * The specified thread local map must be for the current thread. */ @SuppressWarnings("unchecked") public final void remove(InternalThreadLocalMap threadLocalMap) { if (threadLocalMap == null) { return; } Object v = threadLocalMap.removeIndexedVariable(index); removeFromVariablesToRemove(threadLocalMap, this); if (v != InternalThreadLocalMap.UNSET) { try { onRemoval((V) v); } catch (Exception e) { PlatformDependent.throwException(e); } } } /** * Returns the initial value for this thread-local variable. */ protected V initialValue() throws Exception { return null; } /** * Invoked when this thread local variable is removed by {@link #remove()}. */ protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { } }