一、概述
ThreadLocal类是作为线程内部的局部变量而提供的。让这些变量在多线程环境下访问(get/set)时能保证各个线程里的变量相对独立于其他线程内的变量。
通常我们因为程序实现的必要而创建的成员变量大多都是不安全的,因为这些变量被所有的线程共享,由于java内存可见性问题,不同线程对这些共享变量修改后不可见而导致线程安全问题。
通过ThreadLocal创建的变量只能被当前线程访问,对其他线程不可见,故别的线程无法访问和修改,也就是说:对线程公有化变成对线程私有化。事实上每个线程中都有一个ThreadLocal变量副本。
二、应用场景
每个线程都需要一个独享的对象,比如对时间的处理类SimpleDateFormat,如果我们每次使用都new一个对象,很显然这非常的浪费系统性能,同时如果直接作为成员变量,这会被所有线程共享,这又导致线程不安全,要解决这个问题我们可以使用ThreadLocal管理起来就完美了。比如:
可能有的读者朋友已经发现,这样做还不是每个线程创建一个SimpleDateFormat对象吗?这跟直接在方法中new一个对象没有区别啊!然而事实上真的是这样的吗?我么知道1个请求进来就是一个线程,假设有N个线程都在使用dateToStr()方法,如果是直接new的话会产生三个SimpleDateFormat对象,然而用ThreadLocal只会创建一个对象,每一个线程获取一个ThreadLocal对象副本,一个线程一个,这样就实现了同一个变量对线程的私有化。
每个线程内需要保存全局变量,这在一定层级上减少代码耦合度。以上列举为ThreadLocal常见应用场景,而在实际的代码开发中我们还有根据需要进一步的进行细化。
三、核心知识
1、类关系
每个Thread线程对象中都持有一个ThreadLocalMap的成员变量。每个ThreadLocalMap内部又维护由Entry组成的N个节点,这 N个节点也就是Entry数组,每个Entry代表一个完整的对象,Entry对象维护了一堆 k-v对,key是ThreadLocal本身,value是ThreadLocal的泛型值,也就是ThreadLocal的副本。
核心源码如下
2、类关系图
ThreadLocal内存结构图。
3、主要方法
initialValue():初始化,会在get()方法里进行懒加载的。get():得到这个线程对应的value。如果调用get之前没set过,则get内部会执行initialValue方法进行初始化。我们看下 get()方法的源码
set():为这个线程设置一个新值。源码如下:
remove():删除这个线程对应的值,防止内存泄露的最佳手段,这也是《阿里巴巴 JAVA 开发规范》中为什么强调至少调用一次 remove()方法的原因。源码如下:
3.1、initialValue
3.1.1、什么意思
见名知意,初始化一些value(泛型值),就是上文中说的 Entry 对象的 value 值,即ThreadLocal的泛型值,懒加载的。
3.1.2、触发时机
通过上文的描述和对源码的解读可知,该方法是在 get()方法中被触发的。如果在 get()被调用前没有调用过 set()方法,这时候就会在 get()方法内部触发initialValue,通俗讲就是在调用 get 方法的时候没有拿到自己想要的东西,则会触发initialValue。
3.1.3、补充说明
通常,每个线程最多调用一次此方法。但是如果已经调用了remove(),然后再次调用get()的话,则可以再次触发initialValue。如果要重写的话一般建议采取匿名内部类的方式重写此方法,否则默认返回的是null。比如:
3.1.4、源码
3.2、get
3.2.1、什么意思
获取当前线程下的ThreadLocal中的值。
3.2.2、源码
3.3、set
3.3.1、什么意思
其实干的事和initialValue是一样的,都是set值,只是调用时机不同。set是想用就用,api摆在这里,你想用就调一下set方法。很自由。
3.3.2、源码
3.4、remove
3.4.1、什么意思
将当前线程下的ThreadLocal的值删除,目的是为了减少内存占用。主要目的是防止内存泄漏。内存泄漏问题下面会说。
3.4.2、源码
4、ThreadLocalMap
为啥单独拿出来说下,我就是想强调一点:这个东西是归Thread类所有的。它的引用在Thread类里,这也证实了一个问题:ThreadLocalMap类内部为什么有Entry数组,而不是Entry对象?
因为你业务代码能new好多个ThreadLocal对象,各司其职。但是在一次请求里,也就是一个线程里,ThreadLocalMap是同一个,而不是多个,不管你new几次ThreadLocal,ThreadLocalMap在一个线程里就一个,因为再说一次,ThreadLocalMap的引用是在Thread里的,所以它里面的Entry数组存放的是一个线程里你new出来的多个ThreadLocal对象。
文章来源:《电脑编程技巧与维护》 网址: http://www.dnbcjqywh.cn/zonghexinwen/2020/0910/470.html
电脑编程技巧与维护投稿 | 电脑编程技巧与维护编辑部| 电脑编程技巧与维护版面费 | 电脑编程技巧与维护论文发表 | 电脑编程技巧与维护最新目录
Copyright © 2018 《电脑编程技巧与维护》杂志社 版权所有
投稿电话: 投稿邮箱: