1FastThreadLocal的介绍背景和原理介绍既然jdk已经有了ThreadLocal,为什么netty还要自己弄一个FastThreadLocal呢?FastThreadLocal快在哪里?这需要从jdkThreadLocal本身说起。如下图所示:在java线程中,每个线程都有一个ThreadLocalMap实例变量(如果没有使用ThreadLocal,则不会创建这个Map,只有在线程第一次访问ThreadLocal变量时才会创建).Map采用线性检测的方式来解决hash冲突的问题。如果没有找到空闲槽,它将继续向后尝试,直到找到一个空闲槽并插入一个条目。这种方法在经常遇到hash冲突的时候会影响效率。.FastThreadLocal(以下简称ftl)直接使用数组来避免hash冲突的发生。具体方法是:在创建每个FastThreadLocal实例时,分配一个下标索引;分配索引使用AtomicInteger实现,每个FastThreadLocal可以获得唯一的下标。调用ftl.get()方法获取值时,返回直接从数组中获取,比如returnarray[index],如下图所示:2实现源码分析根据上图,ftl的实现涉及到InternalThreadLocalMap、FastThreadLocalThread和FastThreadLocal几个类,从下往上我们先从InternalThreadLocalMap说起。InternalThreadLocalMap类的继承关系图如下:2.1UnpaddedInternalThreadLocalMap的主要属性staticfinalThreadLocalslowThreadLocalMap=newThreadLocal();staticfinalAtomicIntegernextIndex=newVAtomicInteger();Object[]indexedVariable数组用于indexedVariable的存储ftl的值可以直接通过下标访问。nextIndex用于在创建ftl实例时为每个ftl实例分配一个下标,当线程不是ftl时使用slowThreadLocalMap。2.2InternalThreadLocalMap分析InternalThreadLocalMap的主要属性://用来标识数组的slot没有被使用publicstaticfinalObjectUNSET=newObject();/***用来标识ftl变量是否注册了cleaner*BitSet的简要原理:*BitSet默认的底层数据结构是一个长度为1开头的long[]数组,即只有long[0],一个long有64位。*当BitSet.set(1)时,表示long[0]的第二位设置为true,即00000000...0010(64bit),则long[0]==2*当BitSet.get(1),如果第二位为1,表示为真;如果为0,则表示false*BitSet.set(64)时,表示设置第65位,此时long[0]不够用,扩展的地方是long[1],用于存储**存储类似于{index:boolean}键值对,用于防止一个FastThreadLocal多次启动清洗线程*将index位置的位设置为true,表示InternalThreadLocalMap中的FastThreadLocal已经清洗线程启动*/privateBitSetcleanerFlags;privateInternalThreadLocalMap(){super(newIndexedVariableTable());}privatestaticObject[]newIndexedVariableTable(){Object[]array=newObject[32];填充(数组。未设置);returnarray;}比较简单,newIndexedVariableTable()方法创建一个长度为32的数组,初始化为UNSET,然后传递给父类。之后ftl的值就保存在这个数组中。注意这里保存的是直接变量值,不是入口,这点和jdkThreadLocal不同。InternalThreadLocalMap先分析这个,其他的方法后面再分析ftl,再详细说。2.3ftlt的实现分析要发挥ftl的性能优势,必须与ftlt结合使用,否则会退化为jdk的ThreadLocal。ftlt比较简单,关键代码如下:publicclassFastThreadLocalThreadextendsThread{//如果我们有机会包装Runnable,这将被设置为true。privatefinalbooleancleanupFastThreadLocals;私有InternalThreadLocalMapthreadLocalMap;publicfinalInternalThreadLocalMapthreadLocalMap(){返回threadLocalMap;}publicfinalvoidsetThreadLocalMap(InternalThreadLocalMapthreadLocalMap){this.threadLocalMap=threadLocalMap;}}ftlt的trick在于threadLocalMap属性,它继承了javaThread,聚合了自己的InternalThreadLocalMap。访问ftl变量后,对于ftlt线程来说,直接从InternalThreadLocalMap中获取变量值。2.4ftl实现分析ftl实现分析基于netty-4.1.34版本,特声明版本是因为该版本的源码注释掉了ObjectCleaner的调用,与之前的版本不同。2.4.1ftl属性和实例化privatefinalintindex;publicFastThreadLocal(){index=InternalThreadLocalMap.nextVariableIndex();}很简单,就是给属性index赋值,赋值的静态方法在InternalThreadLocalMap中:publicstaticintnextVariableIndex(){intindex=nextIndex.getAndIncrement();如果(index<0){nextIndex.decrementAndGet();抛出新的IllegalStateException(“太多线程本地索引变量”);}返回索引;}可见,每个ftl实例以步长为1的自增序列获取索引值,保证了InternalThreadLocalMap中的数组长度不会突然增加。2.4.2get()方法实现分析publicfinalVget(){InternalThreadLocalMapthreadLocalMap=InternalThreadLocalMap.get();//1对象v=threadLocalMap.indexedVariable(index);//2if(v!=InternalThreadLocalMap.UNSET){返回(V)v;}Vvalue=initialize(threadLocalMap);//3registerCleaner(threadLocalMap);//4返回值;}1.先来看看InternalThreadLocalMap.get()方法如何获取threadLocalMap:=======================InternalThreadLocalMap=======================publicstaticInternalThreadLocalMapget(){Threadthread=Thread.currentThread();if(threadinstanceofFastThreadLocalThread){returnfastGet((FastThreadLocalThread)thread);}else{返回slowGet();}}privatestaticInternalThreadLocalMapfastGet(FastThreadLocalThreadthread){InternalThreadLocalMapthreadLocalMap=thread.threadLocalMap();if(threadLocalMap==null){thread.setThreadLocalMap(threadLocalMap=newInternalThreadLocalMap());}}返回threadLocalMap;因为FastThreadLocalThread只能结合FastThreadLocal的性能优势使用,所以主要依赖于fastGet方法。该方法直接从ftlt线程中获取threadLocalMap,并返回。2.threadLocalMap.indexedVariable(index)很简单,直接从数组中取值,然后返回:publicObjectindexedVariable(intindex){Object[]lookup=indexedVariables;返回索引LocalslowThreadLocalMap.LocalThreadLocalMap.InternalThreadLocalMap.InternalThreadLocalMap.InternalThreadLocalMapret=slowThreadLocalMap.get();if(ret==null){ret=newInternalThreadLocalMap();slowThreadLocalMap.set(ret);}值与对象关系示意图:3ftl的资源回收机制为netty中的ftl提供了三个功能回收机制:自动:使用ftlt执行一个被FastThreadLocalRunnable包装的Runnable任务。任务执行后,ftl会自动清理。手动:ftl和InternalThreadLocalMap都提供了remove方法,用户可以(有时也必须,比如普通线程的线程池使用ftl)手动调用进行显示删除。自动:为当前线程的每个ftl注册一个Cleaner。当thread对象不是stronglyreachable时,Cleanerthread会回收当前线程的currentftl。(netty建议,如果可以使用其他两种方式,就不要再使用这种方式,因为需要另外开启一个线程,比较耗资源,多线程会造成一些资源竞争。在netty-4.1.34版本,已经注释掉了调用ObjectCleaner的代码。)4netty中ftl的使用ftl在netty中最重要的用途就是分配ByteBuf。基本方法是:每个线程分配一块内存(PoolArena)。当需要分配一个ByteBuf时,线程首先从它持有的PoolArena中分配它。如果无法分配,则使用全局分配。但是,由于内存资源有限,仍然可能会有多个线程持有同一个PoolArena。但是,这种做法最大限度地减少了多线程资源竞争,提高了程序效率。具体的代码在PoolByteBufAllocator的内部类PoolThreadLocalCache中:??final?class?PoolThreadLocalCache?extends?FastThreadLocal?{????@Override????????protected?synchronized?PoolThreadCache?initialValue()?{????????????final?PoolArena?heapArena?=?leastUsedArena(heapArenas);????????????final?PoolArena?directArena?=leastUsedArena(directArenas);????????????Thread?current?=?Thread.currentThread();????????????if?(useCacheForAllThreads?||?current?instanceof?FastThreadLocalThread)?{??????????????//?PoolThreadCache即为各个线程持有的内存块的封装????????????????return?new?PoolThreadCache(????????????????????????heapArena,?directArena,?tinyCacheSize,?smallCacheSize,normalCacheSize,????????????????????????DEFAULT_MAX_CACHED_BUFFER_CAPACITY,?DEFAULT_CACHE_TRIM_INTERVAL);????????????}????????????//?No?caching?so?just?use?0?as?sizes.????????????return?new?PoolThreadCache(heapArena,?directArena,?0,?0,?0,?0,?0);????????}????}