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

可能有些人听说过ThreadLocal,但是没有人听说过ThreadLocal对象池

时间:2023-04-01 22:53:32 Java

简介JDK中的Thread想必大家都用过,只要用过异步编程的同学一定对它不陌生。为了保存Thread中特有的变量,JDK引入了ThreadLocal类来专门管理Thread的局部变量。很多ThreadLocal的新手可能不明白ThreadLocal是什么,跟Thread有什么关系。其实很简单。ThreadLocal本质上是一个键,它的值是Thread中一个map中存储的值。每个Thread中都有一个Map,这个Map的类型是ThreadLocal.ThreadLocalMap。这个ThreadLocalMap是如何实现的我们先不详细讨论。现在简单地把它想象成一张地图。接下来我们看下ThreadLocal的工作流程。我们先看ThreadLocal的使用示例:publicclassThreadId{//一个线程ID自增privatestaticfinalAtomicIntegernextId=newAtomicInteger(0);//为每个Thread分配一个线程privatestaticfinalThreadLocalthreadId=newThreadLocal(){@OverrideprotectedIntegerinitialValue(){returnnextId.getAndIncrement();}};//返回当前线程的IDpublicstaticintget(){returnthreadId.get();上面的类有什么用?在不同线程环境下调用ThreadId的get方法时,会返回不同的int值。所以可以看出ThreadId为每个线程生成了一个线程ID。让我们看看它是如何工作的。首先我们调用了ThreadLocal的get方法。ThreadLocal中的get方法定义如下:publicTget(){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")T结果=(T)e.value;返回结果;}}返回setInitialValue();在get方法中,我们首先获取当前线程Thread,然后getMap返回当前Thread中的ThreadLocalMap对象。如果Map不为空,则取出当前ThreadLocal对应的value作为key。如果Map为空,调用初始化方法:privateTsetInitialValue(){Tvalue=initialValue();线程t=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);否则createMap(t,value);返回值;}初始化方法首先判断当前Thread中的ThreadLocalMap是否为空,如果不为空则设置初始值。如果为空,创建一个新的Map:voidcreateMap(Threadt,TfirstValue){t.threadLocals=newThreadLocalMap(this,firstValue);可以看到整个ThreadLocalMap中的Key就是ThreadLocal本身,Value就是ThreadLocal定义泛型的值。现在让我们总结一下ThreadLocal的作用。每个Thread都有一个ThreadLocal.ThreadLocalMap的Map对象。我们想在这个Map中存储一些具体的值,通过具体的对象访问Thread中存储的值。这样的对象就是ThreadLocal。通过ThreadLocal的get方法,可以返回绑定到不同Thread对象的值。在ThreadLocalMap上面我们简单的把ThreadLocalMap当成一个map。其实ThreadLocalMap是一个对象,里面存储的每一个值都是一个Entry。这个Entry不同于Map中的Entry,它是一个静态内部类:staticclassEntryextendsWeakReference>{/**这个ThreadLocal关联的值。*/对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;注意,这里的Entry继承自WeakReference,也就是说这个Entry的key是一个弱引用对象。如果key没有强引用,会在gc中回收。这样就保证了Map中数据的有效性。ThreadLocalMap中的值存放在Entry数组中:privateEntry[]table;让我们看看如何从ThreadLocalMap获取值:privateEntrygetEntry(ThreadLocalkey){inti=key.threadLocalHashCode&(table.length-1);条目e=表[i];if(e!=null&&e.get()==key)返回e;否则返回getEntryAfterMiss(key,i,e);}key.threadLocalHashCode可以简单的认为是ThreadLocal所代表的key的值。而key.threadLocalHashCode&(table.length-1)用于计算当前key在表中的索引。这里使用位运算来提高计算速度。其实这个计算相当于:key.threadLocalHashCode%table.length是取模运算。如果按照取模运算的索引查找,找到了就直接返回。如果没有找到,就会遍历并调用nextIndex方法修改index的值,直到查找完成:privateEntrygetEntryAfterMiss(ThreadLocalkey,inti,Entrye){intlen=标签。长度;while(e!=null){ThreadLocalk=e.get();if(k==key)返回e;如果(k==null)expungeStaleEntry(i);否则i=nextIndex(i,len);e=tab[i];}返回空值;}RecyclerThreadLocal本质上是将ThreadLocal对象绑定到不同的Thread,通过ThreadLocal的Get方法可以获取到不同Thread中存储的值。ThreadLocal和Thread归根结底是一对多的关系。由于ThreadLocal在ThreadLocalMap中是弱引用,当ThreadLocal设置为空时,ThreadLocalMap中对应的对象会在下一次垃圾回收过程中被回收,从而为Thread.LocalMap节省了一块空间。所以当我们的Thread是一个长时间运行的Thread时,如果在这个Thread中分配了很多生命周期短的对象,就会产生很多需要回收的垃圾对象,给垃圾回收器造成压力。为了解决这个问题,netty为我们提供了Recycler类来回收这些短命的对象。接下来,让我们探讨一下Recycler是如何工作的。在此之前,让我们看看如何使用Recycler。公共类MyObject{privatestaticfinalRecyclerRECYCLER=newRecycler(){protectedMyObjectnewObject(Recycler.Handlehandle){returnnewMyObject(handle);}}publicstaticMyObjectnewInstance(inta,Stringb){MyObjectobj=RECYCLER.get();obj.myFieldA=a;obj.myFieldB=b;返回对象;}privatefinalRecycler.Handle句柄;私人诠释myFieldA;私有字符串myFieldB;privateMyObject(Handlehandle){this.handle=handle;}publicbooleanrecycle(){myFieldA=0;myFieldB=空;返回handle.recycle(this);}}MyObjectobj=MyObject.newInstance(42,"foo");...obj.recycle();从本质上讲,Recycler就像一个工厂类,通过它的get方法生成对应的类对象。当对象需要回收时,调用Recycler.Handle中的recycle方法回收对象。先看生成对象的get方法:publicfinalTget(){if(maxCapacityPerThread==0){returnnewObject((Handle)NOOP_HANDLE);}Stackstack=threadLocal.get();DefaultHandlehandle=stack.pop();if(handle==null){handle=stack.newHandle();handle.value=newObject(handle);}返回(T)handle.value;}上面代码的意思就是首先判断是否超过了单线程允许的最大容量,如果超过则返回一个新的对象并绑定一个空的handler,表示新创建的对象不能被回收。如果没有,就从threadLocal获取当前线程绑定的Stack。然后从Stack中取出栈顶元素,如果Stack中没有对象,则新建一个对象并绑定句柄。最后返回句柄绑定的对象。再来看看句柄的回收对象方法recycle:publicvoidrecycle(Objectobject){if(object!=value){thrownewIllegalArgumentException("objectdoesnotbelongtohandle");}Stackstack=this.stack;if(lastRecycledId!=recycleId||stack==null){thrownewIllegalStateException("已回收");}stack.push(这个);上面的代码首先判断句柄绑定的对象是否需要回收Object。只有当它们相等时才会进行回收。回收的本质就是在获取对象的时候将对象压入栈中以供后续使用。所以,Recycler之所以能保存垃圾回收对象的数量,是因为它会把不用的对象存放在Stack中,下次get时返回再用。这就是为什么我们在回收时需要重置对象属性:publicbooleanrecycle(){myFieldA=0;myFieldB=空;返回handle.recycle(this);}总结如果一个线程中有多个同类型的对象生命周期短的对象,那么试试Recycle。本文已收录于http://www.flydean.com/47-netty-thread-…al-object-pool-2/最流行的解读,最深刻的干货,最简洁的教程,很多小东西你不知道的技能等你来发现!欢迎关注我的公众号:《程序那些事儿》,懂技术,更懂你!