Netty 作为高性能框架,对 JDK 中的很多类都进行了封装了和优化,Netty 使用了
    
     FastThreadLocalRunnable
    
    对所有
    
     DefaultThreadFactory
    
    创建出来的
    
     Runnable
    
    都进行了包装。 netty的
    
     FastThreadLocal
    
    和
    
     FastThreadLocalThread
    
    的实现相较于
    
     Thread
    
    和
    
     ThreadLocal
    
    不再发生内存泄漏,据说读性能是 JDK 的 5 倍左右,写入的速度也要快 20% 左右。
   
    ThreadLocal
   
    有人叫它线程本地变量,也叫做线程本地存储。和线程同步机制大有不同,同步采用synchronized关键字和J.U.C中的Lock对象来实现,而加锁的目的是为了能让多个线程安全的共享一个变量,
    
     ThreadLocal
    
    为每个线程创建了自己独有的变量副本,采用空间换时间思想。
   
    一个
    
     ThreadLocal
    
    只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个
    
     ThreadLocal
    
    。
   
{@code ThreadLocal} instances are typically
private static fields in classes
that wish to associate state with a thread (e.g., a user ID or Transaction ID)
    
     用法
    
   
    java8之前
    
    
     private static final ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>() {
     
     
     @Override
     
     protected Integer initialValue() {
     
     
     return 100;
     
     }
     
     };
    
    
    java8中
    
    
     private static final ThreadLocal<Integer> integerThreadLocal = ThreadLocal.withInitial(() -> 100);
    
   
- get():返回此线程局部变量的当前线程副本中的值
- initialValue():返回此线程局部变量的当前线程的“初始值”,默认返回null,供子类重写
- remove():移除此线程局部变量当前线程的值
- set(T value):将此线程局部变量的当前线程副本中的值设置为指定值
    
     实现原理
    
   
    一个
    
     
      Thread
     
    
    类中有这样一个成员变量
    
     ThreadLocal.ThreadLocalMap
    
    
     ,而
    
    
     ThreadLocalMap
    
    是
    
     ThreadLocal
    
    实现线程隔离的精髓。
   
     
   
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);//key存储的是ThreadLocal本身,而value则是实际存储的值
    else
        createMap(t, value);
}
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);//如果hash 冲突了
}
//循环所有的元素,直到找到 key 对应的 entry,如果发现了某个元素的 key 是 null,顺手调用 expungeStaleEntry 方法清理 所有 key 为 null 的 entry
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            return e;
        if (k == null)
            expungeStaleEntry(i);
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}
//ThreadLocalMap的静态内部类
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal<?> k, Object v) {
        super(k);//key被传递WeakReference中
        value = v;
    }
}
public class WeakReference<T> extends Reference<T> {
    public WeakReference(T referent) {
        super(referent);
    }
}
    而
    
     ThreadLocalMap.Entry
    
    实现了实现
    
     <k,v>
    
    存储,并继承
    
     
      WeakReference
     
    
    类(弱引用),gc时
    
     ThreadLocal
    
    (ThreadLocalMap中的Entry的key)会进行回收的。
   
强引用:普通的引用,强引用指向的对象不会被回收
软引用:仅有软引用指向的对象,只有发生gc且内存不足,才会被回收
弱引用:仅有弱引用指向的对象,只要发生gc就会被回收
即便是弱引用也绝非完美,以下2中情况视为泄漏:
- 
     线程的生命周期很长,当
 
 ThreadLocal
 
 没有被外部强引用的时候就会被GC回收,会发生:
 
 ThreadLocalMap
 
 会出现一个
 
 key
 
 为
 
 null
 
 的
 
 Entry
 
 ,但这个
 
 Entry
 
 的
 
 value
 
 将永远没办法被访问到(后续在也无法操作set、get等方法了)。如果当这个线程一直没有结束,那这个
 
 key
 
 为
 
 null的
 
 
 Entry
 
 因为也存在强引用(Entry.value),而
 
 Entry
 
 被当前线程的
 
 ThreadLocalMap
 
 强引用(Entry[] table),导致这个Entry.value永远无法被GC,造成内存泄漏
- 在线程池的场景,程序不停止,线程基本不会销毁
    
     针对第一种情况:
    
    
     
      key为null的value问题好多,怎么破?
     
    
   
    虽然在
    
     ThreadLocalMap
    
    的设计中,已经考虑到这种情况的发生,它提供cleanSomeSlots()和expungeStaleEntry()方法都能清除key为null的value,ThreadLocal的触发点也很特别:set()、get()、remove()方法中都会调用它们。
   
也有不完美的地方(被动清除的方式并不是在所有情况下有效):
- 
     如果
 
 ThreadLocal
 
 的
 
 set()
 
 ,
 
 get()
 
 ,
 
 remove()
 
 方法没有被调用,就会导致
 
 value
 
 的内存泄漏,时刻预防发生gc
- 用static修饰的ThreadLocal,导致ThreadLocal的生命周期和持有它的类一样长,由于ThreadLocal有强引用在,意味着这个ThreadLocal不会被GC。这种情况下,如果不手动删除,Entry的key永远不为null,弱引用就失去了意义,那么执行remove的时候就可以正确进行定位到并且删除。
    
     针对第二种情况:
    
    
     线程池
    
   
使用线程池时归还线程之前记得清除ThreadLocalMap,要不然再取出该线程的时候,ThreadLocal变量还会存在。这就不仅仅是内存泄露的问题了,整个业务逻辑都可能会出错。
    解决方法参考:override
    
     
      ThreadPoolExecutor#afterExecute(r, t)
     
    
    方法,对ThreadLocalMap进行清理。当然ThreadLocal最好还是不要和线程池一起使用。
   
    FastThreadLocal
   
    了解完jdk本身的
    
     ThreadLocal
    
    源码,它使用太麻烦了,易出错,性能也不高!netty对此进行了优化重构,并对jdk原生的线程进行了兼容!
   
    
     FastThreadLocal
    
    有很多优点:
   
- 
     使用了单纯的数组操作来替代了
 
 ThreadLocal
 
 的hash表操作,所以在高并发的情况下速度更快
- 
     set操作,它直接根据index进行数组set。而
 
 ThreadLocal
 
 需要先根据
 
 ThreadLocal
 
 的hashcode计算数组下标,如果发生hash冲突且有无效的Entry时,还要进行Entry的清理和整理操作,不管是否冲突,都要进行一次log级别的Entry回收操作,所以肯定快不了
- 
     get操作,它直接根据index进行获取。而
 
 ThreadLocal
 
 需要先根据
 
 ThreadLocal
 
 的hashcode计算数组下标,然后再根据线性探测法进行get操作,如果不能根据直接索引获取到value的话并且在向后循环遍历的过程中发现了无效的Entry,则会进行无效Entry的清理和整理操作
- 
     remove操作,它直接根据index从数组中删除当前
 
 FastThreadLocal
 
 的value,然后从Set集合中删除当前的
 
 FastThreadLocal
 
 ,之后还可以进行删除回调操作(功能增强)。而
 
 ThreadLocal
 
 需要先根据
 
 ThreadLocal
 
 的hashcode计算数组下标,然后再根据线性探测法进行remove操作,最后还需要进行无效Entry的整理和清理操作。
缺点也有:
    
     FastThreadLocal
    
    较于
    
     ThreadLocal
    
    不好的地方就是内存占用大,不会重复利用已经被删除(用UNSET占位)的数组位置,只会一味增大,是典型的“空间换时间”的操作。
   
    
     使用
    
   
private static final FastThreadLocal<Integer> fastThreadLocal1 = new FastThreadLocal<Integer>(){
    @Override
    protected Integer initialValue() throws Exception {
        return 100;
    }
    @Override
    protected void onRemoved(Integer value) throws Exception {
        System.out.println(value + ":我被删除了");
    }
};
@Test
public void testSetAndGetByCommonThread() {
    Integer x = fastThreadLocal1.get();
    fastThreadLocal1.remove();
    x = fastThreadLocal1.get();//输入null,而ThreadLocal不同一定是有
}
@Test
public void testSetAndGetByFastThreadLocalThread() {
    new FastThreadLocalThread(()->{
        Integer x = fastThreadLocal1.get();
		fastThreadLocal1.set(200);
    }).start();
}
private static final Executor executor = FastThreadExecutors.newCachedFastThreadPool("test");
@Test
public void testSetAndGetByFastThreadLocalThreadExecutor() {
    executor.execute(()->{
        Integer x = fastThreadLocal1.get();
        String s = fastThreadLocal2.get();
        fastThreadLocal1.set(200);
    });
}
    
     数据结构
    
   
    对于jdk的
    
     ThreadLocal
    
    来讲,其底层数据结构就是一个Entry[]数组,key为
    
     ThreadLocal
    
    ,value为对应的值(hash表);通过线性探测法解决hash冲突。
   
    先了解
    
     FastThreadLocalThread
    
    ,每个
    
     FastThreadLocalThread
    
    内部都有一个
    
     InternalThreadLocalMap
    
    ,而
    
     InternalThreadLocalMap
    
    内部存储的key就是
    
     FastThreadLocal
    
    value就是100(上面的),没错,和
    
     ThreadLocal
    
    的设计套路大同小异!但是
    
     InternalThreadLocalMap
    
    底层是单纯的简单数组Object[],初始length==32,数组的第一个元素index=0存储一个
    
     
      Set<FastThreadLocal<?>>
     
    
    的set集合,存储所有有效的
    
     FastThreadLocal
    
    。
   
每当有一个
FastThreadLocal
的value设置到数组中的时候,首先将当前的
FastThreadLocal
对象添加到Object[0]的set集合中,然后将
FastThreadLocal
的value存入Object[]的其余位置(除0以外),而位置也很讲究与
FastThreadLocal
实例属性index对应。
//FastThreadLocal
public V get() {
    // 1、获取InternalThreadLocalMap
    InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
    // 2、从InternalThreadLocalMap获取索引为index的value,如果该索引处的value是有效值,不是占位值,则直接返回
    Object value = threadLocalMap.indexedVariable(index);
    if (value != InternalThreadLocalMap.UNSET) {
        return (V) value;
    }
    // 3、indexedVariables[index]没有设置有效值,执行初始化操作,获取初始值
    V initialValue = initialize(threadLocalMap);
    // 4、注册资源清理器:当该ftl所在的线程不强可达(没有强引用指向该线程对象)时,清理其上当前ftl对象的value和set<FastThreadLocal<?>>中当前的ftl对象
    registerCleaner(threadLocalMap);
    return initialValue;
}
//兼容性
public static InternalThreadLocalMap get() {
    Thread current = Thread.currentThread();
    if (current instanceof FastThreadLocalThread) {
        return fastGet((FastThreadLocalThread) current);
    }
    return slowGet();
}
private static InternalThreadLocalMap fastGet(FastThreadLocalThread current) {
    InternalThreadLocalMap threadLocalMap = current.threadLocalMap();
    if (threadLocalMap == null) {
        threadLocalMap = new InternalThreadLocalMap();
        current.setThreadLocalMap(threadLocalMap);
    }
    return threadLocalMap;
}
/**
 * 兼容非FastThreadLocalThread
 */
private static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<>();
private static InternalThreadLocalMap slowGet() {
    InternalThreadLocalMap threadLocalMap = slowThreadLocalMap.get();
    if (threadLocalMap == null) {
        threadLocalMap = new InternalThreadLocalMap();
        slowThreadLocalMap.set(threadLocalMap);
    }
    return threadLocalMap;
}
private void registerCleaner(InternalThreadLocalMap threadLocalMap) {
    Thread current = Thread.currentThread();
    // 如果已经开启了自动清理功能 或者 已经对threadLocalMap中当前的FastThreadLocal开启了清理线程
    if (FastThreadLocalThread.willCleanupFastThreadLocals(current) || threadLocalMap.isCleanerFlags(index)) {
        return;
    }
    // 设置是否已经开启了对当前的FastThreadLocal清理线程的标志
    threadLocalMap.setCleanerFlags(index);
    // 将当前线程和清理任务注册到ObjectCleaner上去
    ObjectCleaner.register(current, () -> remove(threadLocalMap));
}
    
     回收机制
    
   
提供了三种回收机制:
- 
     自动,执行一个被
 
 FastThreadLocalRunnable
 
 wrap的
 
 Runnable
 
 任务,在任务执行完毕后会自动进行
 
 FastThreadLocal
 
 的清理
- 
     手动,
 
 FastThreadLocal
 
 和
 
 InternalThreadLocalMap
 
 都提供了remove方法,在合适的时候用户可以(有的时候也是必须,例如普通线程的线程池使用
 
 FastThreadLocal
 
 )手动进行调用,进行显示删除
- 
     自动,为当前线程的每一个
 
 FastThreadLocal
 
 注册一个Cleaner,当线程对象不强可达的时候,该Cleaner线程会将当前线程的当前ftl进行回收
    netty推荐使用前两种方式,第三种方式需要另起线程,耗费资源,而且多线程就会造成一些资源竞争。
    
   
 
