当前位置: 首页 > 后端技术 > Java

ThreadLocal学习一个比喻

时间:2023-04-01 20:02:25 Java

关于ThreadLocal有一个形象的比喻:体育馆里的公共储物柜,这里的储物柜相当于threadLocalMap,柜子的key就像一个threadLocal的引用,去的人健身房就像每个线程。每个人都有一把分配给自己的钥匙,通过钥匙可以找到对应的储物柜,存放自己的私人物品。当一个人使用完储物柜后,储物柜可以分配给后续的人使用。threadLocal、threadLocalMap、entry[]的关系,thread在threadLocal类中有一个内部类threadLocalMap,entry[]是threadLoacalMap中的一个内部类,它继承了WeakReference,有一个Object类型的v??alue属性。线程类有一个threadLocal.threadLocalMap属性。staticclassThreadLocalMap{staticclassEntryextendsWeakReference>{/**与此ThreadLocal关联的值。*/对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}.....}使用的时候需要用staticfinal修饰吗?先说一下fianl,使用final修饰其实是一个‘通病’,跟threadLocal没有直接关系:基本类型不允许被修饰;引用类型不能指向其他对象。用static修饰的变量是类变量。无论创建多少个对象,它始终是一个对象。ThreaLocal是一种以空间换时间的思想。使用静态修饰后,就是减少对象的创建,减少空间占用。当然如何使用还是需要结合业务场景。setmethodpublicvoidset(Tvalue){//获取当前线程Threadt=Thread.currentThread();//取出上面线程中的threadLocalMapThreadLocalMapmap=getMap(t);if(map!=null)//如果threadLocal是staticfinal,这里的this会是同一个threadLocalmap.set(this,value);否则createMap(t,value);}privatevoidset(ThreadLocalkey,Objectvalue){Entry[]tab=table;intlen=tab.length;//获取入口数组下标inti=key.threadLocalHashCode&(len-1);//在循环中,先取出下标对应的入口对象,然后判断该对象是否为空。//for中的语句3是寻找下一个入口,即碰撞后,采用openfixed//寻址方式的线性检测(HashMap采用链表方式)。for(Entrye=tab[i];e!=null;e=tab[i=nextIndex(i,len)]){ThreadLocalk=e.get();//对应下标下有一个值,如果引用相等,则覆盖if(k==key){e.value=value;返回;}//表示当前entry没有线程,或者已经执行了remove方法,//如果再次使用,直接使用,做一些清理工作if(k==null){replaceStaleEntry(key,value,我);返回;}}//对应的下标值为空,说明没有冲突,直接把value放入entry数组tab[i]=newEntry(key,value);intsz=++大小;//如果清洗失败,Entry个数已经达到链表扩容阈值的2/3,则扩容if(!cleanSomeSlots(i,sz)&&sz>=threshold)rehash();}其他哈希冲突可以通过开放寻址方式中的线性检测来解决。如果在get查询的时候有冲突的key,会继续向后遍历,比较key得到对应的value。在扩容过程中,entry逻辑会被清理,新的entry数组的长度会增加一倍,达到旧数组的两倍。同时,在计算出新的哈希值后,旧的数据会被移动到新的条目中。在get、set、rehash、remove方法中,如果满足条件,也会进行清理。内存泄漏问题在线程池场景下,线程使用后不会销毁,而是归还到池中。如果使用ThreadLocal,线程会一直持有threadlocal,即thread->threadlocal->threadlocalmap->entry这样的引用链接,只有在调用remove方法时,key才会被设置为null,key为弱引用,GC时回收。再说说InheritableThreadLocal主线程派生子线程后,子线程中获取不到主线程中threadLocal中放置的值,子线程中可以取出InheritableThreadLocal中放置的值。在创建Thread对象时,会判断父线程中的inheritableThreadLocals是否不为空,如果不为空,则将父线程中的inheritableThreadLocals中的数据复制到自己的inheritableThreadLocals中。这样就实现了父线程和子线程的上下文传递:privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,longstackSize,AccessControlContextacc,booleaninheritThreadLocals){......if(inheritThreadLocals&&parent.inheritableThreadLocals!=null)this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);......}这样就解决了父子线程之间的传值问题,但是这个方法在线程池中不起作用,可以这样做:在创建Task的时候,使用一个变量来保存业务线程中inheritThreadLocal设置的值,然后在线程池中线程执行时将上一步保存的值赋值给当前线程。例子可以参考:线程池InheritableThreadLocal问题,另外阿里的TransmittableThreadLocal已经封装好了。在使用TransmittableThreadLocal的时候,需要注意一点:在使用TransmittableThreadLocal的时候,需要使用TtlExecutors来封装线程池。说明:ThreadLocal系列(三)——TransmittableThreadLocal使用及原理分析终于说到FastThreadLocal了,大家可以参考一下:原来这是比ThreadLocal更快的东西。它不再使用hash算法,也不再使用map数据结构,而是一个Object数组。数组的第一个位置存放了一个set对象,然后数组后面的值放在该位置,在fastthreadlocal中有一个指向该值的指针,这样就可以对应了,如图下图。为每个FastThreadLocal生成唯一索引,避免ThreadLocal中的hash冲突。get和set方法会注册后台任务,使用后台线程清理,而ThreadLocal会同步清理,会影响get和set的性能。(但都建议手动清理)扩展时不再需要Rehash。FastThreadLocal使用字节填充来解决错误共享。