FastThreadLocal
FastThreadLocal是Netty对jdk中ThreadLocal进行封装和优化,解决jdk中自带的ThreadLocal 在线程池使用环境中,有内存泄漏的风险问题。它和ThreadLocal有着一样的功能,从名称来看Fast是快的意思,那么它为什么快?下面我们来讨论一下
如何使用
@Test
public void test() {
// 1.创建FastThreadLocal
FastThreadLocal<String> fastThreadLocal = new FastThreadLocal<String>();
// 2.赋值
fastThreadLocal.set("hello fastThreadLocal");
// 3.获取值
System.out.println(fastThreadLocal.get());
// 4.删除
fastThreadLocal.remove();
System.out.println(fastThreadLocal.get());
}
测试结果:
hello fastThreadLocal
null
1. 创建FastThreadLocal
每创建一个FastThreadLocal都会自动生成index索引,这个index作用很大,后面会说到,先留着
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
//InternalThreadLocalMap.nextVariableIndex()
public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement();
if (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
2. 赋值 set()
public final void set(V value) {
if (value != InternalThreadLocalMap.UNSET) {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
if (setKnownNotUnset(threadLocalMap, value)) {
registerCleaner(threadLocalMap);
}
} else {
remove();
}
}
2.1 InternalThreadLocalMap.get() 从当前线程中获取InternalThreadLocalMap
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;
}
先判断当前线程是不是FastThreadLocalThread,若是通过fastGet直接获取,这个方法很简单,先获取,没有就创建,若不是,则通过slowGet方法获取,这个方法主要是用JDK的ThreadLocal来保存InternalThreadLocalMap,逻辑和fasetGet相似,这里就不在赘述了。
2.2 setKnownNotUnset()
private boolean setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
if (threadLocalMap.setIndexedVariable(index, value)) {
addToVariablesToRemove(threadLocalMap, this);
return true;
}
return false;
}
将value数据插入到InternalThreadLocalMap中 indexedVariables,这个index就是实例化FastThreadLocal生成的(唯一的),当数组满时,就需要扩容了
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;
}
}
// 每次扩容是以前的2倍
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;
}
当数据插入进去,就执行addToVariablesToRemove(), 该方法主要是将自己本身(FastThreadLocal)放入到InternalThreadLocalMap中,当然首先先判断有没有放入进去。
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);
}
这里为什么要FastThreadLocal放入到InternalThreadLocalMap,原因是避免了内存泄漏的风险问题,后面会谈到。
2.3 registerCleaner()
注册清除器, 避免了内存泄漏的风险问题
private void registerCleaner(final InternalThreadLocalMap threadLocalMap) {
Thread current = Thread.currentThread();
if (!FastThreadLocalThread.willCleanupFastThreadLocals(current)) {
// 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.
}
});
}
}
2.3.1 先判断 thread instanceof FastThreadLocalThread && ((FastThreadLocalThread) thread).willCleanupFastThreadLocals();当前线程是否需要注册内存清除器,若需要进入ObjectCleaner.register()方法
public static void register(Object object, Runnable cleanupTask) {
AutomaticCleanerReference reference = new AutomaticCleanerReference(object,
ObjectUtil.checkNotNull(cleanupTask, "cleanupTask"));
LIVE_SET.add(reference);
if (CLEANER_RUNNING.compareAndSet(false, true)) {
Thread cleanupThread = new FastThreadLocalThread(CLEANER_TASK);
cleanupThread.setPriority(Thread.MIN_PRIORITY);
cleanupThread.setContextClassLoader(null);
cleanupThread.setName(CLEANER_THREAD_NAME);
cleanupThread.setDaemon(false);
cleanupThread.start();
}
}
2.3.1.1. 先创建AutomaticCleanerReference对象,这个AutomaticCleanerReference是继承WeakReference对象的
2.3.1.2. 将AutomaticCleanerReference对象 添加到LIVE_SET中
2.3.1.3. 这个清理线程只执行一次,CLEANER_TASK 是无限循环Runnable,这个死循环会不断的判断LIVE_SET中数据是否没有被引用,若没有,直接销毁FastThreadLocal数据,避免了内存泄漏的风险问题。最终会调用FastThreadLocal.remove()
private static final Runnable CLEANER_TASK = new Runnable() {
@Override
public void run() {
boolean interrupted = false;
for (;;) {
while (!LIVE_SET.isEmpty()) {
final AutomaticCleanerReference reference;
try {
reference = (AutomaticCleanerReference) REFERENCE_QUEUE.remove(REFERENCE_QUEUE_POLL_TIMEOUT_MS);
} catch (InterruptedException ex) {
interrupted = true;
continue;
}
if (reference != null) {
try {
reference.cleanup();
} catch (Throwable ignored) {
}
LIVE_SET.remove(reference);
}
}
CLEANER_RUNNING.set(false);
if (LIVE_SET.isEmpty() || !CLEANER_RUNNING.compareAndSet(false, true)) {
break;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
}
};
2.3.2 当不需要清理线程处理FastThreadLocal remove,就表示当前线程是FastThreadLocalThread 线程,那么它是怎么避免内存溢出问题呢?先看看FastThreadLocalThread的构造方法
public FastThreadLocalThread(Runnable target) {
super(FastThreadLocalRunnable.wrap(target));
cleanupFastThreadLocals = true;
}
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);
}
}
注意到Runnable这个对象被封装成FastThreadLocalRunnable,当Runnable执行了run方法之后,回去执行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[variablesToRemove.size()]);
for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
tlv.remove(threadLocalMap);
}
}
} finally {
InternalThreadLocalMap.remove();
}
}
1.先获取当前线程的InternalThreadLocalMap
2.通过Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex); 获取FastThreadLocal
3.执行FastThreadLocal.remove()
4.清除FastThreadLocal 在InternalThreadLocalMap的数组中
以上就解释为什么要记录自身(FastThreadLocal)放在InternalThreadLocalMap的数组中,就是因为在当线程执行完成后,清除FastThreadLocal,避免内存溢出。
3. get()
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;
}
1.先获取当前InternalThreadLocalMap
2.根据index获取相应的缓存值
3.若不是NULL,直接返回,若是NULL则通过initialize() 去获取,当时initialize方法是用户自定义的
4.注册线程清除器,上面已聊过
4. remove()
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);
}
}
}
1. 获取缓存对象
2. 设置InternalThreadLocalMap的数组为NULL
3. 清除FastThreadLocal 在InternalThreadLocalMap的数组中
4. onRemoval() 是空方法,需要用户自定义实现
总结一下
FastTheadLocal与ThreadLocal比较:
优点: 1. FastTheadLocal性能也会比 JDK 的好,因为它是用数组,而JDK 的使用线性探测法的 Map
2. FastTheadLocal避免内存泄漏问题
缺点: 每个FastThreadLocal的index都是唯一的, 当FastThreadLocal下标很大的时候,需要扩容,感觉有点浪费