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

”ThreadLocal“ 本地线程变量是什么,怎么用

时间:2023-04-01 18:58:42 Java

什么是“ThreadLocal”局部线程变量以及如何使用它?ThreadLocal称为局部线程变量,也就是说ThreadLocal中填充的是当前线程的变量,与其他线程是封闭隔离的。ThreadLocal在每个线程中创建一个变量的副本,这样每个线程都可以访问自己内部的副本变量。使用ThreadLocal的场景也有很多。相信大家在日常开发中经常会遇到:1、在跨层传递对象时,使用ThreadLocal可以避免多次传递,打破层与层之间的约束。2、线程间数据隔离3、进行事务操作,存储线程事务信息。4.数据库连接,Session会话管理。2.如何使用ThreadLocal?首先,在定义ThreadLocal的时候,我们还可以定义ThreadLocal中存储的具体类型的对象。ThreadLocalthreadLocalValue=newThreadLocal<>();上面我们定义了一个存储Integer的ThreadLocal对象。在ThreadLocal中存储和获取对象也很简单,只需要使用get()和set():threadLocalValue.set(1);整数结果=threadLocalValue.get();我们可以把ThreadLocal看成一个map,当前线程就是map中的key。除了使用set方法给ThreadLocal赋值外,我们还可以使用默认的初始化方法来赋值。ThreadLocal提供的静态方法withInitial初始化一个ThreadLocal。ThreadLocalthreadLocal=ThreadLocal.withInitial(()->1);删除ThreadLocal中存储的数据,可以调用:threadLocal.remove();3.ThreadLocal源码分析3.1set方法/***设置当前线程的这个线程局部变量的副本*为指定值。大多数子类将不需要*覆盖此方法,仅依靠{@link#initialValue}*方法来设置线程局部变量的值。**@paramvalue要存储在当前线程的副本中的值*此线程本地。*/publicvoidset(Tvalue){//首先获取当前线程对象Threadt=Thread.currentThread();//获取线程中变量ThreadLocal.ThreadLocalMapThreadLocalMapmap=getMap(t);//如果不为空,if(map!=null)map.set(this,value);else//如果为空,则初始化线程对象的map变量,key为当前线程局部变量createMap(t,value);我们可以看到ThreadLocal内部有一个ThreadLocalMap类。当我们在ThreadLocal中存储数据时,实际上是在ThreadLocal中存储数据dLocalMap用于数据访问3.2get方法/***返回此线程局部变量的当前线程副本中的值。如果变量对于当前线程没有值,它首先被初始化为返回的值*通过调用{@link#initialValue}方法。**@return这个线程局部的当前线程的值*/publicTget(){//获取当前线程Threadt=Thread.currentThread();//从ThreadLocalMap数据中获取当前线程ThreadLocalMapmap=getMap(t);if(map!=null){ThreadLocalMap.Entrye=map.getEntry(this);if(e!=null){@SuppressWarnings("unchecked")Tresult=(T)e.value;返回结果;}}返回setInitialValue();}4。ThreadLocal内存泄漏问题4.1在理解ThreadLocal内存泄漏之前,我们需要了解什么是强引用和弱引用:强引用在程序代码中很常见,对于像Objectobj=newObject()这样的引用,只要是强引用仍然存在,垃圾收集器永远不会回收引用的对象。弱引用用于描述非本质对象,但其强度比软引用强较弱的、与弱引用关联的对象只能存活到下一次垃圾回收发生。当垃圾回收器工作时,不管当前内存是否足够,只与弱引用相关联的对象都会被回收。JDK1.2之后,提供了WeakReference类来实现弱引用。使用代码验证importjava.lang.ref.WeakReference;公共类TestWeakReference{@Overrideprotectedvoidfinalize()throwsThrowable{super.finalize();System.out.println("finalize方法执行完毕");}publicstaticvoidmain(String[]args){TestWeakReferencetwr=newTestWeakReference();WeakReferencewr=newWeakReference(twr);/***此时TestSoftReference的一个对象有两个引用指向它它:*1.强引用twr*2.弱引用sr*/System.out.println("beforegc:"+wr.得到());twr=空;//去除强引用twrSystem.gc();System.out.println("gc之后:"+wr.get());}}得到如下结果:gc之前:com.reference.test.TestWeakReference@15db9742gc之后:nullfinalize方法executedThreadLocal一个Thread维护了一个ThreadLocalMap,key是使用弱引用的ThreadLocal实例,value是线程这些对象之间的引用关系如下。ThreadLocalMap使用ThreadLocal的弱引用作为key。如果一个ThreadLocal没有外部强引用引用它,那么系统GC的时候这个ThreadLocal就会被回收。这样,ThreadLocalMap中就会存在key为null的entry,而这些key为null的entry的value是没有办法访问的。如果当前线程长时间没有结束,那么这些key为null的entry的value总会有一个强引用链。:ThreadRef->Thread->ThreaLocalMap->Entry->value永远无法回收,导致内存泄漏。在ThreadLocal中,我们通过它的Entity源码可以看出,ThreadLocalMap在使用ThreadLocal作为key存储数据时,使用的是弱引用。/***此哈希映射中的条目扩展了WeakReference,使用*它的主要ref字段作为键(它始终是一个*ThreadLocal对象)。请注意,空键(即entry.get()*==null)意味着不再引用该键,因此可以从表中删除*条目。这些条目在后面的代码中被称为*作为“陈旧条目”。*/staticclassEntryextendsWeakReference>{/**与此ThreadLocal关联的值。*/对象值;条目(ThreadLocal<?>k,对象v){超级(k);值=v;}}4.2既然弱引用会导致内存泄漏,为什么要使用弱引用呢?下面我们讨论两种情况:Key使用强引用:被引用的ThreadLocal对象被回收,但是ThreadLocalMap仍然持有ThreadLocal的强引用。如果不手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。key使用弱引用:被引用的ThreadLocal对象被回收。由于ThreadLocalMap持有ThreadLocal的弱引用,所以即使不手动删除ThreadLocal也会被回收。下次ThreadLocalMap调用set、get和remove时,该值将被清除。对比两种情况,我们可以发现:由于ThreadLocalMap的生命周期和Thread一样长,如果不手动删除对应的key,会造成内存泄漏,但是使用弱引用可以多一层保护:ThreadLocal的弱引用不会造成内存泄漏,下次ThreadLocalMap调用set、get、remove时会清空对应的值。因此,ThreadLocal内存泄漏的根本原因是:由于ThreadLocalMap的生命周期与Thread一样长,如果不手动删除对应的key,就会造成内存泄漏,并不是因为弱引用。4.3如何避免内存泄漏每次使用ThreadLocal时,都会调用其remove()方法来清除数据。在使用线程池的情况下,如果不及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的可能会导致业务逻辑出现问题。所以,使用ThreadLocal就相当于加锁后解锁,使用后清理。一般在业务场景中,我们会用到一个Spring拦截器类:publicclassTokenInterceptorimplementsHandlerInterceptor{},它有三个重载方法:preHandlepostHandleafterCompletionpreHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandle)方法,顾名思义,这个方法会在在请求处理之前调用。postHandle(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandle,ModelAndViewmodelAndView)方法,从preHandle方法的解释我们知道,这个方法,包括后面要说的afterCompletion方法,只能在当前的preHandle方法中返回true拦截器只能在什么时候被调用。postHandle方法,顾名思义,是在当前请求处理完之后,也就是调用Controller方法之后执行的,但是会在DispatcherServlet渲染回来视图之前调用,所以我们可以在之后对ModelAndView对象进行操作Controller已经在这个方法中处理了它。afterCompletion(HttpServletRequestrequest,HttpServletResponseresponse,Objecthandle,Exceptionex)方法,只有对应Interceptor的preHandle方法的返回值为true时才会执行该方法。顾名思义,这个方法会在整个请求结束后执行,也就是DispatcherServlet渲染出对应的view之后。该方法的主要作用是进行资源清理。说到这里大家应该知道什么时候调用ThreadLocal的remove操作了。是的,最常用的Spring框架已经为我们提供了合适的调用时机。在afterCompletion中调用ThreadLocal的remove操作是业务比较合适的场景。5、建议ThreadLocal无法解决共享对象的更新问题,建议对ThreadLocal对象使用静态修饰。这个变量是一个线程中所有操作共享的,所以设置为静态变量,所有这样的实例共享这个静态变量,也就是说类在第一次加载时,只占用一块存储空间被分配,所有这样的实例对象(只要是在这个线程中定义的)都可以操作这个变量。