ThreadLocal(1.0)
ThreadLocal
ThreadLocal 是什么
概述
ThreadLocal 本地线程变量,线程自带的变量副本(实现了每一个线程副本都有一个专属的本地变量,主要解决的就是让每一个线程绑定自己的值,自己用自己的,不跟别人争抢。通过使用get()和set()方法,获取默认值或将其值更改为当前线程所存的副本的值从而避免了线程安全的问题)
- synchronized 或者 lock ,有个管理员,好比现在大家签到,多个同学(线程),但是只有一只笔,只能同一个时间,只有一个线程(同学)签到,加锁(同步机制是以时间换空间,执行时间不一样,类似于排队)
- ThreadLocal,人人有份,每个同学手上都有一支笔,自己用自己的,不用再加锁来维持秩序。以空间换时间,为每一个线程提供一份变量的副本,从而实现同时访问,互不干扰,效率自然比 lock 高;
api 介绍
protected T initialValue():initialValue()
:返回此线程局部变量的当前线程的初始值
。(对于 initialValue() 较为老旧,jdk1.8又加入了 withInitial() 方法)static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
:创建线程局部变量T get()
:返回当前线程的此线程局部变量的副本中的值void set(T value)
:将当前线程的此线程局部变量的副本设置为指定的值void remove()
:删除此线程局部变量的当前线程的值
api 运用
1 | package tech.chen.juccode.a15; |
运行结果
0 卖出 1
2 卖出 1
3 卖出 2
1 卖出 7
总共卖出11
小总结:
因为每个 Thread 内有自己的实例副本且该副本只由当前线程自己使用
既然其他 Thread 不可访问,那就不存在多线程共享的问题
统一设置初始值,但是每个线程对这个值的修改都是各自线程互相独立的
加入 synchronized 或者 lock 控制线程的访问顺序,而 ThreadLocal 人手一份,大家各自安好,没必要抢夺
ThreadLocal 分析
Thread、ThreadLocal、ThreadLocalMap 三者有何关联?
可见,Thread 中有一个 ThreadLocalMap 属性 ,点进 ThreadLocalMap
发现 ThreadLocalMap 是 ThreadLocal 的静态内部类
概括来讲:
- Thread类中有一个ThreadLocal.ThreadLocalMap threadLocals = null的变量,这个 ThreadLocal 相当于是 Thread 类ThreadLocalMap 的桥梁,在 ThreadLocal 中有静态内部类 ThreadLocalMap , ThreadLocalMap 中有 Entry 数组
- 当我们为 threadLocal 变量赋值,实际上就是以当前 threadLocal 实例为 key ,值为 value 的 Entry 往这个 threadLocalMap 中存放
- t.threadLocals = new ThreadLocalMap(this, firstValue) 如下这行代码,可以知道每个线程都会创建一个ThreadLocalMap对象,每个线程都有自己的变量副本
1 | //核心代码说明 |
set方法详解
- 首先获取当前线程,并根据当前线程获取一个Map
- 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)
- 如果Map为空,则给该线程创建 Map,并设置初始值
1 | /** |
get方法详解
先获取当前线程的 ThreadLocalMap 变量,如果存在则返回值,不存在则创建并返回初始值
1 | /** |
remove方法详解
- 首先获取当前线程,并根据当前线程获取一个 Map
- 如果获取的 Map 不为空,则移除当前 ThreadLocal 对象对应的 entry
1 | /** |
ThreadLocal内存泄漏问题
为什么使用弱引用?
弱引用:只要 jvm 发生 gc ,弱引用对象就会被回收
当方法执行完毕后,栈帧销毁强引用 tl 。但此时线程的 ThreadLocalMap 里某个 entry 的 key 引用还指向这个对象
若这个 key 引用是强引用,就会导致key指向的 ThreadLocal 对象及 v 指向的对象不能被 gc 回收,造成内存泄漏
若这个 key 引用是弱引用就大概率会减少内存泄漏的问题。使用弱引用,就可以使 ThreadLocal 对象在方法执行完毕后顺利被回收且 Entry 的 key 引用指向为null,这就解决了 ThreadLocal 内存泄露的问题,但是 entry 中的 value指向的对象呢?
key为null时的entry
ThreadLocalMap使用 ThreadLocal 的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,可以 key-value 结构中的 key为null了,但是 value 呢?
ThreadLocalMap 中就会出现 key 为null 的 Entry,根据源码我们知道,没有 ThreadLocal 我们是无法访问 value的,如果当前线程再迟迟不结束的话(比如正好用在线程池),这些 key 为 null 的Entry的value就会一直存在一条强引用链,导致 value 指向的对象 长时间无法被回收。特别是使用线程池时;
因此弱引用不能 100% 保证内存不泄露。我们要在不使用某个 ThreadLocal 对象后,手动调用 remmove 方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的 ThreadLocalMap对象也是重复使用的,如果我们不手动调用 remove 方法,那么后面的线程就有可能获取到上个线程遗留下来的 value 值,造成bug
如果当前 thread 运行结束 ,threadLocal,threadLocalMap, Entry 没有引用链可达,在垃圾回收的时候都会被系统进行回收
但在实际使用中我们有时候会用线程池去维护我们的线程,比如在 Executors.newFixedThreadPool() 时创建线程的时候,为了复用线程是不会结束的,所以 threadLocal 内存泄漏就值得我们小心
另外要知道的是:set、get、remove方法会去检查所有键为null的Entry对象 ,当发现key 为 null 时,会去清除 value指向的对象
ThreadLocal小总结
ThreadLocal 本地线程变量,以空间换时间,线程自带的变量副本,人手一份,避免了线程安全问题
每个线程持有一个只属于自己的专属 Map 并维护了 Thread Local 对象与具体实例的映射,该Map由于只被持有它的线程访问,故不存在线程安全以及锁的问题
ThreadLocalMap 的 Entry 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题
都会通过 expungeStaleEntry,cleanSomeSlots, replace StaleEntry 这三个方法回收键为 null 的 Entry 对象的值(即为具体实例)以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法
用完之后一定要 remove 操作