ThreadLocal--以副本的方式办理并发以及隔离题目

藏宝库编辑 2024-10-1 14:24:26 104 0 来自 中国
岂论是Atomic照旧synchronized大概Lock,着实都是采用同步的方式(串行大概自旋等)办理了线程安全题目。这里我们将先容别的一种办理线程安全题目的思路----副本的方式。
假如你有一个全局共享的变量,那么多线程并发的时间,对这个共享变量的访问是不安全的。方法内的局部变量是线程安全的,由于每个线程都会有本身的副本。也就是说局部变量被封闭在线程内部,别的线程无法访问(引用型有所区别)。那么有没有作用域介于两者之间,既能包管线程安全,又不至于只范围于方法内部的方式呢?答案是肯定的,我们使用ThreadLocal就可以做到这一点。ThreadLocal变量的作用域是为线程,也就是说线程内跨方法共享。比方某个对象的方法A对threadLocal变量赋值,在同一个线程中的别的一个对象的方法B可以大概读取到该值。由于作用域为同一个线程,那么自然就是线程安全的。但是须要留意的是,假如threadLocal存储的是共享变量的引用,那么同样会有线程安全题目。
1、ThreadLocal 的使用场景

ThreadLocal的特性决定了它的使用场景。由于ThreadLocal中存储的变量是线程隔离的,以是一样寻常在以下环境使用ThreadLocal:
1、存储须要在线程隔离的数据。比如线程实行的上下文信息,每个线程是差别的,但是对于同一个线程来说会共享同一份数据。Spring MVC的 RequestContextHolder 的实现就是使用了ThreadLocal;
2、跨层通报参数。条理分别在软件计划中十分常见。条理分别后,体如今代码层面就是每层负责差别职责,一个完备的业务使用,会由一系列差别层的类的方法调用串起来完成。有些时间第一层获得的一个变量值大概在第三层、以致更深层的方法中才会被使用。假如我们不借助ThreadLocal,就只能一层层地通过方法参数举行通报。使用ThreadLocal后,在第一层把变量值生存到ThreadLocal中,在使用的条理方法中直接从ThreadLocal中取出,而不用作为参数在差别方法中传来传去。不外万万不要滥用ThreadLocal,它的本意并不是用来跨方法共享变量的。团结第一种环境,我们放入ThreadLocal跨层通报的变量一样寻常也是具有上下文属性的。比如用户的信息等。如许我们在AOP处置惩罚非常大概其他使用时可以很方便地获取当前登任命户的信息。
2、怎样使用 ThreadLocal

ThreadLocal使用起来非常简单,我们先看一个简单的例子。
可以看到每个线程为同一个ThreadLocal对象set差别的值,但各个线程打印出来的仍旧是本身生存进去的值,并没有被别的线程所覆盖。
一样寻常来说,在实践中,我们会把ThreadLocal对象申明为static final,作为私有变量封装到自界说的类中。别的提供static的set和get方法。如下面的代码:
public final class OperationInfoRecorder {    private static final ThreadLocal<OperationInfoDTO> THREAD_LOCAL = new ThreadLocal<>();    private OperationInfoRecorder() {    }    public static OperationInfoDTO get() {        return THREAD_LOCAL.get();    }    public static void set(OperationInfoDTO operationInfoDTO) {        THREAD_LOCAL.set(operationInfoDTO);    }    public static void remove() {        THREAD_LOCAL.remove();    }}如许做的目的有二:

  • 1、static 确保全局只有一个生存OperationInfoDTO对象的ThreadLocal实例;
  • 2、final 确保ThreadLocal的实例不可更改。防止被不测改变,导致放入的值和取出来的不同等。别的还能防止ThreadLocal的内存走漏,详细缘故原由下文中会有讲授。
使用的时间可以在任何方法的任何位置调用OperationInfoRecorder的set大概get方法,生存和取出。如下面代码:
OperationInfoRecorder.set(operationInfoDTO)OperationInfoRecorder.get()3、ThreadLocal源代码剖析

学习到这里,你肯定很好奇ThreadLocal是怎样做到多个线程对同一个对象set使用,但只会get出本身set进去的值呢?这个征象有点违背我们的认知。接下来我们就从set方法入手,来看看ThreadLocal的源代码:
   public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }一眼看过去,一下就可以看到map。没错,假如ThreadLocal可以大概生存多个线程的变量值,那么它肯定是借助容器来实现的。
这个map不是一样寻常的map,可以看到它是通过当前线程对象获取到的ThreadLocalMap。看到这里应该看出些端倪,这个map着实是和Thread绑定的。接下来我们看getMap方法的代码:
    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }原来这个ThreadLocal就存方法Thread对象上。下面我们看看Thread中的干系代码:
    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;注释中写的很清晰,这个属性由ThreadLocal来维护。threadLocals的访问控制决定在包外是无法直接访问的。以是我们在使用的时间只能通过ThreadLocal对象来访问。
set时,会把当前threadLocal对象作为key,你想要生存的对象作为value,存入map。


看到这里,我们大至已司理清了ThreadLocal和Thread的关系,我们看下图:我们接下来分析get方法,代码如下:
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();}get方法也是先取适当前线程对象中生存的ThreadLocalMap对象,然后使用当前threadLocal对象从map中取得相应的value。
每个Thread的ThreadMap以threadLocal作为key,生存本身线程的value副本。我们可以这么来明确ThreadLocal,着实ThreadLocal对象是你要真正生存对象的身份代表。而这个身份在每个线程中对应的值,着实是生存在每个线程中,并没有生存在ThreadLocal对象中。
这里可以举个例子,学校里要每班评选一名学习标兵,一名道德标兵。班主任会举行评选然跋文录下来。门生标兵及道德标兵的身份就是两个ThreadLocal对象,而每个班主任是一个线程,记录的评选效果的小本子就是ThreadLocalMap对象。每个班主任会在本身的小本子上记录下评选效果,比如说一班班主任记录:道德标兵:小明,学习标兵:小红。二班班主任记录:道德标兵:小赵,学习标兵:小岩。通过这个例子各人应该很清晰ThreadLocal的原理了。
ThreadLocal的计划真的非常巧妙,看似本身生存了每个线程的变量副本,着实每个线程的变量副本是生存在线程对象中,那么自然就线程隔离了。云云分析起来,是不是有一种ThreadLocal没做什么变乱,却抢了头功的感觉?着实不然。Thread对象中用来生存变量副本的ThreadLocalMap的界说就在ThreadLocal中。我们接下来分析ThreadLocalMap的源代码。
4、ThreadLocalMap分析

ThreadLocalMap是ThreadLocal的静态内部类。ThreadLocalMap的功能着实是和HashMap雷同的,但是为什么不直接使用HashMap呢?在ThreadLocalMap中使用WeakReference包装后的ThreadLocal对象作为key,也就是说这里对ThreadLocal对象为弱引用。当ThreadLocal对象在ThreadLocalMap引用之外,再无其他引用的时间可以大概被垃圾接纳。如下面代码所示:
static class Entry extends WeakReference<ThreadLocal<?>> {            /** The value associated with this ThreadLocal. */            Object value;            Entry(ThreadLocal<?> k, Object v) {                super(k);                value = v;            }        }如许做会带来新的题目。假如ThreadLocal对象被接纳,那么ThreadLocalMap中生存的key值就变成了null,而value会一直被Entry引用,而Entry又被threadLocalMap对象引用,threadLocalMap对象又被Thread对象所引用,那么当Thread一直不闭幕的话,value对象就会一直驻留在内存中,直至Thread被烧毁后,才会被接纳。这就是ThreadLocal引起内存走漏题目。

而ThreadLocalMap在计划的时间也思量到这一点,在get和set的时间,会把遇到的key为null的entry清算掉。不外如许做是依靠于我们背面临ThreadLocal的一连使用也不能100%包管可以大概清算干净,假如我们在秒杀服务中使用,有大概造成内存的刹时打满。
通常,我们可以通过以下两种方式来避免这个题目:

  • 1、把ThreadLocal对象声明为static,如许ThreadLocal成为了类变量,生命周期不是和对象绑定,而是和类绑定,延伸了声明周期,避免了被接纳;
  • 2、在使用完ThreadLocal变量后,手动remove掉,防止ThreadLocalMap中Entry一直保持对value的强引用。导致value不能被接纳。
  • 3、淘汰侵害,只管不要在ThreadLocal中放大对象
4、总结

通过本节学习,我们掌握了ThreadLocal 的原理和其使用场景。绝大多数环境下,ThreadLocal用于存储和线程干系的上下文信息,也就是线程共享的信息,便于同一线程的差别方法中取值,而不用作为方法参数层层通报。
使用的时间须要留意几个常见的题目1.内存走漏 2.上下文丢失(常见于线程池,并行流)3.数据交互污染(常见于线程池)
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-11-22 04:55, Processed in 0.221697 second(s), 33 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表