ThreadLocal的八个关键知识点

网站建设3年前发布
30 00

大家好,我是捡田螺的小男孩。,无论是工作还是面试中,我们都会跟ThreadLocal打交道,今天就跟大家聊聊ThreadLocal的八个关键知识点哈~,ThreadLocal,即线程本地变量。如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是在操作自己本地内存里面的变量,从而起到线程隔离的作用,避免了并发场景下的线程安全问题。,并发场景下,会存在多个线程同时修改一个共享变量的场景。这就可能会出现线性安全问题。,为了解决线性安全问题,可以用加锁的方式,比如使用synchronized 或者Lock。但是加锁的方式,可能会导致系统变慢。加锁示意图如下:,ThreadLocal的八个关键知识点,还有另外一种方案,就是使用空间换时间的方式,即使用ThreadLocal。使用ThreadLocal类访问共享变量时,会在每个线程的本地,都保存一份共享变量的拷贝副本。多线程对共享变量修改时,实际上操作的是这个变量副本,从而保证线性安全。,ThreadLocal的八个关键知识点,日常开发中,ThreadLocal经常在日期转换工具类中出现,我们先来看个反例:,我们在多线程环境跑DateUtil这个工具类:,运行后,发现报错了:,ThreadLocal的八个关键知识点,如果在DateUtil工具类,加上ThreadLocal,运行则不会有这个问题:,运行结果:,刚刚反例中,为什么会报错呢?这是因为SimpleDateFormat不是线性安全的,它以共享变量出现时,并发多线程场景下即会报错。,为什么加了ThreadLocal就不会有问题呢?并发场景下,ThreadLocal是如何保证的呢?我们接下来看看ThreadLocal的核心原理。,为了有个宏观的认识,我们先来看下ThreadLocal的内存结构图,ThreadLocal的八个关键知识点,从内存结构图,我们可以看到:,对照着几段关键源码来看,更容易理解一点哈~我们回到Thread类源码,可以看到成员变量ThreadLocalMap的初始值是为null,ThreadLocalMap的关键源码如下:,ThreadLocal类中的关键set()方法:,ThreadLocal类中的关键get()方法,所以怎么回答ThreadLocal的实现原理?如下,最好是能结合以上结构图一起说明哈~,Thread线程类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,即每个线程都有一个属于自己的ThreadLocalMap。,ThreadLocalMap内部维护着Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型值。,并发多线程场景下,每个线程Thread,在往ThreadLocal里设置值的时候,都是往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而可以实现了线程隔离。,了解完这几个核心方法后,有些小伙伴可能会有疑惑,ThreadLocalMap为什么要用ThreadLocal作为key呢?直接用线程Id不一样嘛?,举个代码例子,如下:,这种场景:一个使用类,有两个共享变量,也就是说用了两个ThreadLocal成员变量的话。如果用线程id作为ThreadLocalMap的key,怎么区分哪个ThreadLocal成员变量呢?因此还是需要使用ThreadLocal作为Key来使用。每个ThreadLocal对象,都可以由threadLocalHashCode属性唯一区分的,每一个ThreadLocal对象都可以由这个对象的名字唯一区分(下面的例子)。看下ThreadLocal代码:,然后我们再来看下一个代码例子:,再对比下这个图,可能就更清晰一点啦:,ThreadLocal的八个关键知识点,我们先来看看TreadLocal的引用示意图哈:,ThreadLocal的八个关键知识点,关于ThreadLocal内存泄漏,网上比较流行的说法是这样的:,ThreadLocalMap使用ThreadLocal的弱引用作为key,当ThreadLocal变量被手动设置为null,即一个ThreadLocal没有外部强引用来引用它,当系统GC时,ThreadLocal一定会被回收。这样的话,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话(比如线程池的核心线程),这些key为null的Entry的value就会一直存在一条强引用链:Thread变量 -> Thread对象 -> ThreaLocalMap -> Entry -> value -> Object 永远无法回收,造成内存泄漏。,当ThreadLocal变量被手动设置为null后的引用链图:,ThreadLocal的八个关键知识点,实际上,ThreadLocalMap的设计中已经考虑到这种情况。所以也加上了一些防护措施:即在ThreadLocal的get,set,remove方法,都会清除线程ThreadLocalMap里所有key为null的value。,源代码中,是有体现的,如ThreadLocalMap的set方法:,如ThreadLocal的get方法:,到这里,有些小伙伴可能有疑问,ThreadLocal的key既然是弱引用.会不会GC贸然把key回收掉,进而影响ThreadLocal的正常使用?,弱引用:具有弱引用的对象拥有更短暂的生命周期。如果一个对象只有弱引用存在了,则下次GC将会回收掉该对象(不管当前内存空间足够与否),其实不会的,因为有ThreadLocal变量引用着它,是不会被GC回收的,除非手动把ThreadLocal变量设置为null,我们可以跑个demo来验证一下:,结论就是,小伙伴放下这个疑惑了,哈哈~,给大家来看下一个内存泄漏的例子,其实就是用线程池,一直往里面放对象,运行结果出现了OOM,tianLuoThreadLocal.remove();加上后,则不会OOM。,我们这里没有手动设置tianLuoThreadLocal变量为null,但是还是会内存泄漏。因为我们使用了线程池,线程池有很长的生命周期,因此线程池会一直持有tianLuoClass对象的value值,即使设置tianLuoClass = null;引用还是存在的。这就好像,你把一个个对象object放到一个list列表里,然后再单独把object设置为null的道理是一样的,列表的对象还是存在的。,所以内存泄漏就这样发生啦,最后内存是有限的,就抛出了OOM了。如果我们加上threadLocal.remove();,则不会内存泄漏。为什么呢?因为threadLocal.remove();会清除Entry,源码如下:,有些小伙伴说,既然内存泄漏不一定是因为弱引用,那为什么需要设计为弱引用呢?我们来探讨下:,通过源码,我们是可以看到Entry的Key是设计为弱引用的(ThreadLocalMap使用ThreadLocal的弱引用作为Key的)。为什么要设计为弱引用呢?,ThreadLocal的八个关键知识点,我们先来回忆一下四种引用:,我们先来看看官方文档,为什么要设计为弱引用:,我再把ThreadLocal的引用示意图搬过来:,ThreadLocal的八个关键知识点,下面我们分情况讨论:,因此可以发现,使用弱引用作为Entry的Key,可以多一层保障:弱引用ThreadLocal不会轻易内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。,实际上,我们的内存泄漏的根本原因是,不再被使用的Entry,没有从线程的ThreadLocalMap中删除。一般删除不再使用的Entry有这两种方式:,我们知道ThreadLocal是线程隔离的,如果我们希望父子线程共享数据,如何做到呢?可以使用InheritableThreadLocal。先来看看demo:,可以发现,在子线程中,是可以获取到父线程的 InheritableThreadLocal 类型变量的值,但是不能获取到 ThreadLocal 类型变量的值。,获取不到ThreadLocal 类型的值,很好理解,因为它是线程隔离的嘛。InheritableThreadLocal 是如何做到的呢?原理是什么呢?,在Thread类中,除了成员变量threadLocals之外,还有另一个成员变量:inheritableThreadLocals。它们两类型是一样的:,Thread类的init方法中,有一段初始化设置:,可以发现,当parent的inheritableThreadLocals不为null时,就会将parent的inheritableThreadLocals,赋值给前线程的inheritableThreadLocals。说白了,就是如果当前线程的inheritableThreadLocals不为null,就从父线程哪里拷贝过来一个过来,类似于另外一个ThreadLocal,数据从父线程那里来的。有兴趣的小伙伴们可以在去研究研究源码~,ThreadLocal的很重要一个注意点,就是使用完,要手动调用remove()。,而ThreadLocal的应用场景主要有以下这几种:,彻底理解ThreadLocal[1],ThreadLocal是如何导致内存泄漏的[2],深入分析 ThreadLocal 内存泄漏问题[3],[1]彻底理解ThreadLocal: https://www.cnblogs.com/xzwblog/p/7227509.html,[2]ThreadLocal是如何导致内存泄漏的: https://zhuanlan.zhihu.com/p/346291694,[3]深入分析 ThreadLocal 内存泄漏问题: https://www.jianshu.com/p/1342a879f523

© 版权声明

相关文章