1 FastThreadLocal
FastThreadLocal, which can perform better than traditional JDK ThreadLocal when fetching objects from FastThreadLocalThreads.
FastThreadLocal uses a constant index to index arrays, which provides a slight performance improvement over the traditional use of Hashcode and Hash table. The improvement is not noticeable, but it can be useful in high frequency scenarios.
1.1 set method
1.1.1 Method signature
io.netty.util.concurrent.FastThreadLocal#set(V)
1.1.2 code
/**
* Set the value for the current thread.
*/
public final void set(V value) {
if (value != InternalThreadLocalMap.UNSET) {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
setKnownNotUnset(threadLocalMap, value);
} else {
remove();
}
}
Copy the code
As if it is value = InternalThreadLocalMap UNSET, suggests that is to be deleted, we first look at not to delete,
Look at the InternalThreadLocalMap. The get (); What’s in it?
public static InternalThreadLocalMap get() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return fastGet((FastThreadLocalThread) thread); } else { return slowGet(); }}Copy the code
As we saw above, fastGet is called if the current thread is a FastThreadLocalThread, so look at the code for fastGet:
private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
if (threadLocalMap == null) {
thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
}
return threadLocalMap;
}
Copy the code
If the InternalThreadLocalMap object in the FastThreadLocalThread is empty, a new one will be created.
Look at what the constructor for InternalThreadLocalMap does:
private InternalThreadLocalMap() {
indexedVariables = newIndexedVariableTable();
}
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
Arrays.fill(array, UNSET);
return array;
}
private static final int INDEXED_VARIABLE_TABLE_INITIAL_SIZE = 32;
public static final Object UNSET = new Object();
Copy the code
New an array of 32 elements and fill it with UNSET.
Then back to see io.net ty. Util. Concurrent. FastThreadLocal# set (V), have a look
setKnownNotUnset(threadLocalMap, value); The code:Copy the code
/** * @see InternalThreadLocalMap#setIndexedVariable(int, Object). */ private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) { if (threadLocalMap.setIndexedVariable(index, value)) { addToVariablesToRemove(threadLocalMap, this); } } private final int index; public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); } @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); }Copy the code
In the code above, we see that each FastThreadLocal maintains a globally unique index;
Related code in InternalThreadLocalMapCopy the code
/**
* @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;
}
Copy the code
As we saw above, FastThreadLocal calls the setIndexedVariable method of InternalThreadLocalMap to store the object value at the index position of the array stored in InternalThreadLocalMap.
And if index exceeds the size of the array, the array is doubled in size to ensure that the value can be stored at index.
And we can also see that the first location of the array in InternalThreadLocalMap maintains a Set that holds FastThreadLocal. If FastThreadLocal first stores a variable in a thread, it will be put there.
Where we use IdentityHashMap, the difference between this and hashMap is that IdentityHashMap compares keys by reference equality, while hashMap compares keys by reference equals.
1.2 the get method
1.2.1 Method signature
@SuppressWarnings("unchecked")
public final V get()
Copy the code
1.2.2 code
/** * 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; } return initialize(threadLocalMap); } public Object indexedVariable(int index) { Object[] lookup = indexedVariables; return index < lookup.length? lookup[index] : UNSET; } 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; } /** * Returns the initial value for this thread-local variable. */ protected V initialValue() throws Exception { return null; }Copy the code
As we saw above, if the array of InternalThreadLocalMap in the thread is UNSET or the array is not larger than index, FastThreadLocal does not place objects on the current FastThreadLocalThread.
This calls Initialize to initialize the object, of course only returning null by default, and the current FastThreadLocal is added to the VariablesToRemove collection.
1.3 remove
1.3.1 Method signature
public final void remove()
Copy the code
1.3.2 code
/** * Sets the value to uninitialized for the specified thread local map. * After this, any subsequent call to get() will trigger a new call to initialValue(). */ public final void remove() { remove(InternalThreadLocalMap.getIfSet()); } /** * Sets the value to uninitialized for the specified thread local map. * After this, any subsequent call to get() will trigger a new 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); } } } 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); } protected void onRemoval(@SuppressWarnings("UnusedParameters") V value) throws Exception { }Copy the code
The corresponding code in InternalThreadLocalMap:Copy the code
public static InternalThreadLocalMap getIfSet() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { return ((FastThreadLocalThread) thread).threadLocalMap(); } return slowThreadLocalMap.get(); } public Object removeIndexedVariable(int index) { Object[] lookup = indexedVariables; if (index < lookup.length) { Object v = lookup[index]; lookup[index] = UNSET; return v; } else { return UNSET; }}Copy the code
As you can see above, we can overwrite onRemoval to listen for which objects have been removed and remove the current FastThreadLocal from the VariablesToRemove set.
2 FastThreadLocalThread
public class FastThreadLocalThread extends Thread { // This will be set to true if we have a chance to wrap the Runnable. private final boolean cleanupFastThreadLocals; private InternalThreadLocalMap threadLocalMap; / /... The following is omitted because they are both get sets and constructorsCopy the code
3 QA
3.1 Why to maintain the set VariablesToRemove?
A: Now that we’ve looked at the core code of FastThreadLocal and FastThreadLocalThread, what do you feel is missing from the traditional JDK ThreadLocal? Also missing is the weak reference element.
Traditional ThrealLocal
An Entry in the.threadLocalmap is a soft reference to a ThreadLocal. It guarantees that if a ThreadLocal is globally weakly reachable, it guarantees that ThreadLocal objects and objects created by the ThreadLocal in all threads, They were recycled in time.
Background — Summary of Java references.
So we assume that the set VariablesToRemove is also used to do that.
And then we’re going to test this hypothesis to see if it works.
Let’s review when FastThreadLocal is added to VariablesToRemove:
- FastThreadLocal puts variables into the thread the first time it stores them.
-
Initialize theCopy the code
When FastThreadLocal is removed from VariablesToRemove:
Remove the
Therefore, we see the semantics of the VariablesToRemove set, which is stored in the collection of FastThreadLocal that currently holds objects in the thread.
Then let’s take a look at the variable in FastThreadLocalThread that we missed, cleanupFastThreadLocals,
See how it works
public FastThreadLocalThread(Runnable target) {
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
Copy the code
Look at the code for FastThreadLocalRunnable:
final class FastThreadLocalRunnable implements Runnable { private final Runnable runnable; private FastThreadLocalRunnable(Runnable runnable) { this.runnable = ObjectUtil.checkNotNull(runnable, "runnable"); } @Override public void run() { try { runnable.run(); } finally { FastThreadLocal.removeAll(); } } static Runnable wrap(Runnable runnable) { return runnable instanceof FastThreadLocalRunnable ? runnable : new FastThreadLocalRunnable(runnable); }}Copy the code
See FastThreadLocal. RemoveAll ();
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[0]); for (FastThreadLocal<? > tlv: variablesToRemoveArray) { tlv.remove(threadLocalMap); } } } finally { InternalThreadLocalMap.remove(); } } @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); }}}Copy the code
InternalThreadLocalMap.remove();
Copy the code
public static void remove() { Thread thread = Thread.currentThread(); if (thread instanceof FastThreadLocalThread) { ((FastThreadLocalThread) thread).setThreadLocalMap(null); } else { slowThreadLocalMap.remove(); }}Copy the code
As we see FastThreadLocal. RemoveAll (); The InternalThreadLocalMap is cleared from the thread.Copy the code
So back to the question at the beginning: Why maintain the set VariablesToRemove?
This is because at the end of the Runnable, we need to clear out all FastThreadLocal objects left by the task in the thread.
So at first we suspect that this is not true, maintaining the set VariablesToRemove simply removes all FastThreadLocal objects at the end of Runnable. It has nothing to do with soft references.