什么是ThreadLocal
- ThreadLocal提供了线程局部变量. 这些变量和正常的变量不同,因为每一个线程在访问ThreadLocal实例的时候 都有自己独立的 变量副本.
- ThreadLocal实例通常是类的私有静态字段,使用它的目的是希望将状态(用户ID、事务ID) 与线程关联起来
- 通俗易懂: 实现每一个线程都有自己的专属本地变量副本
ThreadLocal能解决什么问题
- 我们知道解决线程安全问题,要么用synchronzied、ReentrantLock、或者用原子类, 这类解决方法说白了都是通过加锁的方式
- 而 ThreadLocal则是 每个线程都存有自己的本地变量副本,各玩各的 互不打扰
ThreadLocal需要注意的点
-
使用完ThreadLocal一定要手动的remove
- 如果是使用线程池处理业务,会存在线程复用的情况,上一段业务处理的结果没有清空 会影响到下一段业务,造成数据错乱,严重的会造成内存溢出
- 代码
- 下面的submit执行逻辑 如果没有手动remove,就会导致累加操作无限下去,
class Number { ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0); public void add(){ threadLocal.set(threadLocal.get() + 1); } } public class ThreadLocal003 { public static void main(String[] args) { ExecutorService executorService = Executors.newFixedThreadPool(3); Number number = new Number(); for (int i = 0;i < 10;i++){ executorService.submit(() -> { try { Integer beforeInt = number.threadLocal.get(); number.add(); Integer afterInt = number.threadLocal.get(); System.out.println(Thread.currentThread().getName() + "\t\t beforeInt: " + beforeInt + "---afterInt: " + afterInt); } finally { number.threadLocal.remove(); } }); } } }
-
为什么ThreadLocal会有内存泄露的风险
-
什么是内存泄漏
- 不再会被使用的对象或者变量占用的内存不能被回收
-
弱引用修饰ThreadLocal 保证了 Entry对象中的key 指向ThreadLocal对象能被及时回收,但是 v指向的对象是需要调用set get 方法发现key是null才会回收整个Entry, 因此弱引用不能百分比解决内存泄漏问题.
- 我们不使用某个ThreadLocal对象后,一定要调用remove方法删除它
- 尤其是线程池中,不仅仅是内存泄漏的问题,因为线程池中的线程是复用的,意味着线程的ThreadLocalMap对象也是重复使用的,如果不手动调用remove方法,那么后面的线程就有可能获取到上一个线程遗留下来的value值,造成bug
-
-
为什么ThreadLocalMap 里的entry对象 需要继承WeakReference(弱引用)
- 栈 强引用指向ThreadLocal 对象, ThreadLocalMap 引用指向ThreadLocal对象
- 如果是entry对象是强引用 指向ThreadLocal, 一旦栈的强引用被回收后, entry对象key指向ThreadLocal不能被gc回收,造成内存泄漏
- 如果是弱引用,就会减少内存泄漏的问题
ThreadLocal源码分析
-
Thread、ThreadLocal、ThreadLocalMap之间的关系
- ThreadLocalMap是ThreadLocal的静态内部类
- Thread类中存在 ThreadLocalMap属性
- ThreadLocalMap 是一个保存ThreadLocal对象的map(ThreadLocal作为key)
-
属性说明
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; /* * InheritableThreadLocal values pertaining to this thread. This map is * maintained by the InheritableThreadLocal class. */ ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
-
公共方法
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } private T setInitialValue() { // 初始值是null T value = initialValue(); Thread t = Thread.currentThread(); // 获取map // map如果是null 则创建map,key是ThreadLocal value是 初始值value 长度大小默认是18 // map不是null,则 put进去 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
-
get方法
public T get() { // 获取到当前线程 Thread t = Thread.currentThread(); // 当前线程的ThreadLocalMap 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(); }
总结、
- ThreadLocal并不解决线程间共享数据的问题
- 适用于变量在线程间隔离且在方法间共享的场景
- ThreadLocal在各个线程创建了独立的变量副本避免了线程安全问题
- ThreadLocalMap的Entry对 ThreadLocal的引用为弱引用, 避免了ThreadLocal无法被回收的问题
- get set remove方法都会通过exoungStaleEntry方法回收k = null的键, 从而防止value内存泄漏