/*
 * 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.ObjectCleaner;
import io.netty.util.internal.PlatformDependent;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

A special variant of ThreadLocal that yields higher access performance when accessed from a FastThreadLocalThread.

Internally, a 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.

To take advantage of this thread-local variable, your thread must be a FastThreadLocalThread or its subtype. By default, all threads created by DefaultThreadFactory are FastThreadLocalThread due to this reason.

Note that the fast path is only possible on threads that extend 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 ThreadLocal.

Type parameters:
  • <V> – the type of the thread-local variable
See Also:
/** * 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 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.
/** * 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.
/** * 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 FastThreadLocal variables accessed from non-FastThreadLocalThreads. 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.
/** * 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
/** * Returns the current value for the current thread */
@SuppressWarnings("unchecked") public final V get() { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); Object v = threadLocalMap.indexedVariable(index); if (v != InternalThreadLocalMap.UNSET) { return (V) v; } V value = initialize(threadLocalMap); registerCleaner(threadLocalMap); return value; } private void registerCleaner(final InternalThreadLocalMap threadLocalMap) { Thread current = Thread.currentThread(); if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlagSet(index)) { return; } threadLocalMap.setCleanerFlag(index); // We will need to ensure we will trigger remove(InternalThreadLocalMap) so everything will be released // and FastThreadLocal.onRemoval(...) will be called. ObjectCleaner.register(current, new Runnable() { @Override public void run() { remove(threadLocalMap); // It's fine to not call InternalThreadLocalMap.remove() here as this will only be triggered once // the Thread is collected by GC. In this case the ThreadLocal will be gone away already. } }); }
Returns the current value for the specified thread local map. The specified thread local map must be for the current thread.
/** * 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.
/** * Set the value for the current thread. */
public final void set(V value) { if (value != InternalThreadLocalMap.UNSET) { InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); if (setKnownNotUnset(threadLocalMap, value)) { registerCleaner(threadLocalMap); } } else { remove(); } }
Set the value for the specified thread local map. The specified thread local map must be for the current thread.
/** * 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) { setKnownNotUnset(threadLocalMap, value); } else { remove(threadLocalMap); } }
Returns:see InternalThreadLocalMap.setIndexedVariable(int, Object).
/** * @return see {@link InternalThreadLocalMap#setIndexedVariable(int, Object)}. */
private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) { if (threadLocalMap.setIndexedVariable(index, value)) { addToVariablesToRemove(threadLocalMap, this); return true; } return false; }
Returns true if and only if this thread-local variable is set.
/** * Returns {@code true} if and only if this thread-local variable is set. */
public final boolean isSet() { return isSet(InternalThreadLocalMap.getIfSet()); }
Returns true if and only if this thread-local variable is set. The specified thread local map must be for the current thread.
/** * 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().
/** * 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.
/** * 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.
/** * 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 remove().
/** * Invoked when this thread local variable is removed by {@link #remove()}. */
protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { } }