Why Netty Introduced FastThreadLocal and How It Boosts Performance
FastThreadLocal, Netty’s custom thread‑local implementation, replaces JDK’s ThreadLocal by using an indexed array to avoid hash collisions, offering faster access; this article explains its background, core classes, source‑code mechanics, performance trade‑offs, and cleanup strategies within Netty’s architecture.
1 FastThreadLocal Background and Principle Overview
Although JDK already provides ThreadLocal, Netty implements its own FastThreadLocal (ftl) to achieve higher performance. The key advantage is avoiding hash collisions by using a simple indexed array.
In a Java thread, each thread holds a ThreadLocalMap instance (created only when a ThreadLocal variable is first accessed). This map resolves hash collisions via linear probing, which can degrade efficiency under frequent collisions.
FastThreadLocal directly uses an array to eliminate hash collisions. Each FastThreadLocal instance receives a unique index during creation, allocated via an AtomicInteger. When ftl.get() is called, the value is retrieved directly from the array:
return array[index];2 Implementation Source Analysis
The implementation involves several classes: InternalThreadLocalMap, FastThreadLocalThread, and FastThreadLocal. We start from InternalThreadLocalMap.
2.1 UnpaddedInternalThreadLocalMap Main Fields
static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
static final AtomicInteger nextIndex = new AtomicInteger();
Object[] indexedVariables;The indexedVariables array stores FastThreadLocal values; nextIndex provides a unique index for each FastThreadLocal instance.
2.2 InternalThreadLocalMap Analysis
Key fields include:
// Marker for unused slots
public static final Object UNSET = new Object();
/**
* BitSet indicating whether a FastThreadLocal has registered a cleaner.
* The BitSet is backed by a long[] array; each bit represents a FastThreadLocal index.
*/
private BitSet cleanerFlags;The method newIndexedVariableTable() creates a 32‑element array filled with UNSET and passes it to the superclass:
static Object[] newIndexedVariableTable() {
Object[] array = new Object[32];
Arrays.fill(array, UNSET);
return array;
}Values are stored directly in this array, unlike JDK ThreadLocal which stores Entry objects.
2.3 FastThreadLocalThread Analysis
FastThreadLocalThreadextends Thread and holds an InternalThreadLocalMap instance:
public final InternalThreadLocalMap threadLocalMap() {
return threadLocalMap;
}
public final void setThreadLocalMap(InternalThreadLocalMap threadLocalMap) {
this.threadLocalMap = threadLocalMap;
}This allows FastThreadLocal variables to be accessed directly from the thread’s own map.
2.4 FastThreadLocal Implementation
2.4.1 Properties and Instantiation
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}Each instance obtains a unique index from InternalThreadLocalMap.nextVariableIndex(), which increments an atomic counter.
2.4.2 get() Method
public final V get() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get(); // 1
Object v = threadLocalMap.indexedVariable(index); // 2
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
V value = initialize(threadLocalMap); // 3
registerCleaner(threadLocalMap); // 4
return value;
}Step 1 obtains the thread‑local map (fast path for FastThreadLocalThread, slow path otherwise). Step 2 reads the value from the indexed array. If the slot is UNSET, the value is initialized and a cleaner may be registered.
2.4.3 Internal get() Logic
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;
}The fast path returns the map stored in FastThreadLocalThread; the slow path uses a regular ThreadLocal to hold the map.
2.4.4 indexedVariable Method
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length ? lookup[index] : UNSET;
}Direct array access provides O(1) retrieval.
2.4.5 initialize Method
private V initialize(InternalThreadLocalMap threadLocalMap) {
V v = null;
try {
v = initialValue();
} catch (Exception e) {
PlatformDependent.throwException(e);
}
threadLocalMap.setIndexedVariable(index, v); // 3‑1
addToVariablesToRemove(threadLocalMap, this); // 3‑2
return v;
}It obtains the initial value, stores it in the indexed array, and registers the FastThreadLocal for later removal.
2.4.6 registerCleaner Method (Netty 4.1.34)
private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
Thread current = Thread.currentThread();
if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlagSet(index)) {
return;
}
threadLocalMap.setCleanerFlag(index);
// The ObjectCleaner registration is commented out in this version.
}In this Netty version the cleaner registration code is disabled, leaving only a flag update.
2.5 Performance Degradation on Ordinary Threads
If a thread is not a FastThreadLocalThread, FastThreadLocal falls back to the JDK ThreadLocal behavior because the thread lacks an InternalThreadLocalMap. The slow path creates or retrieves a map via a regular ThreadLocal, then accesses the indexed array, which adds overhead compared to the fast path.
3 FastThreadLocal Resource Reclamation Mechanisms
Netty provides three reclamation strategies:
Automatic : When a FastThreadLocalRunnable wrapped task finishes, the associated FastThreadLocal is automatically cleared.
Manual : Users can explicitly call remove() on FastThreadLocal and its map, which is recommended for thread‑pool scenarios.
Cleaner‑based : Registers a cleaner that runs when the thread becomes unreachable. Netty advises avoiding this in version 4.1.34 because the cleaner code is commented out and it incurs extra thread overhead.
4 FastThreadLocal Usage in Netty
The most important use case is allocating ByteBuf objects. Each thread holds a PoolArena; when a ByteBuf is needed, the thread first allocates from its own arena, falling back to a global arena if necessary. This reduces contention and improves throughput.
The concrete implementation resides in PoolByteBufAllocator.PoolThreadLocalCache, which extends FastThreadLocal<PoolThreadCache>:
final class PoolThreadLocalCache extends FastThreadLocal<PoolThreadCache> {
@Override
protected synchronized PoolThreadCache initialValue() {
final PoolArena<byte[]> heapArena = leastUsedArena(heapArenas);
final PoolArena<ByteBuffer> directArena = leastUsedArena(directArenas);
Thread current = Thread.currentThread();
if (useCacheForAllThreads || current instanceof FastThreadLocalThread) {
return new PoolThreadCache(heapArena, directArena, tinyCacheSize, smallCacheSize,
normalCacheSize, DEFAULT_MAX_CACHED_BUFFER_CAPACITY, DEFAULT_CACHE_TRIM_INTERVAL);
}
// No caching – use zero sizes.
return new PoolThreadCache(heapArena, directArena, 0, 0, 0, 0, 0);
}
}This cache enables each thread to reuse memory buffers efficiently, further leveraging FastThreadLocal’s low‑overhead access pattern.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Architect's Must-Have
Professional architects sharing high‑quality architecture insights. Covers high‑availability, high‑performance, high‑stability designs, big data, machine learning, Java, system, distributed and AI architectures, plus internet‑driven architectural adjustments and large‑scale practice. Open to idea‑driven, sharing architects for exchange and learning.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
