ThreadLocal使用场景及内存泄漏

  1. 定义
  2. 如何使用它们
  3. 为什么ThreadLocal能实现线程变量隔离
  4. ThreadLocal的问题(可能内存泄漏)
  5. ThreadLocal带来的好处
  6. 主要应用场景

定义

ThreadLocal官方定义: 该类提供线程局部变量。这些线程局部变量与普通变量的不同,每个线程都有自己独立初始化的变量副本(通过其get或set方法)。
如果希望将类的局部变量和线程状态关联(如 user id 或者 transcation id ), 就可以使用ThreadLocal.

如何使用它们

ThreadLocal的简单示例

private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "default");
public static void main(String[] args) throws InterruptedException {
        threadLocal.set("in main class");
        System.out.println("1 " + threadLocal.get());
        ExecutorService service = Executors.newFixedThreadPool(1);
        service.submit(() -> {
            System.out.println("2 " + threadLocal.get());
            threadLocal.set("in sub thread");
            System.out.println("3 " + threadLocal.get());
        });
        service.shutdown();
        Thread.sleep(500);
        System.out.println("4 " + threadLocal.get());
    }
//输出: 1 in main class
//2 null
//3 in sub thread
//4 in main class

从上面的示例可以看出, 在不同的线程中对ThreadLocal变量进行set和get操作时, 它们的变量是相互独立的.

为什么ThreadLocal能实现线程变量隔离

从源码中可知, 每一个Thread内部都有一个ThreadLocalMap, ThreadLocalMap里面 Key是当前ThreadLocal对象, Value是线程对应的变量副本, ThreadLocal所做的事情就是 负责维护每个Thread内部的ThreadLocalMap.
ThreadLocal的核心方法

T get();   // 获取本地线程变量
void set(T value) // 设置本地线程变量
void remove(); // 移除本地线程变量

get()的执行内容:
1. 根据Thread.currentThread() 获取当前执行线程信息, 并根据当前线程对象获取该线程的ThreadLocalMap对象
2. 若map不为空, 直接根据当前ThreadLocal对象获取entry, 进入过去value副本并返回
3. 若map为空, 将线程变量副本写入null, 并返回null.

set()的执行内容:
1. 根据Thread.currentThread() 获取当前执行线程信息, 并根据当前线程对象获取该线程的ThreadLocalMap对象
2. 若map不为空, 将ThreadLocal对象和值写入到当前线程对应的map中.
3. 若map为空, 则需要初始化当前线程的ThreadLocalMap对象, 再写入ThreadLocal和value的副本.

remove()的执行内容:
1. 获取当前线程的ThreadLocalMap并移除Key为当前ThreadLocal对象的键值对.

ThreadLocal的问题(可能内存泄漏)

当ThreadLocal存储了很多 Key = null 的Entry时, 但长时间不结束当前thread 且不再调用get(), set(), remove() 方法, 那么当ThreadLocal对象被回收后, Key是弱引用也被回收, 但是Value是强引用, 一直不回收, 就容易导致内存泄漏.
如何解决该问题: 每次调用完ThreadLocal之后, 都调用remove()方法, 手动销毁数据.

ThreadLocal带来的好处

  1. ThreadLocal把操作的数据存储在了线程本地, 直接本地内存读取, 不再需要synchronized等同步的方式修改主内存的数据, 然后再复制到本地内存, 提高了访问效率. 这也是为什么ThreadLocal能解决线程安全的问题.
    ThreadLocal和线程同步的一些异同: 同步机制采用了“以时间换空间”的方式: 串行访问数据, 并共享对象. 而ThreadLocal采用了“以空间换时间”的方式: 并行访问,独享对象.

主要应用场景

  1. 在维护每个线程的Session的时候, ThreadLocal能帮助我们在每个线程中独享资源.
  2. 数据库连接在多线程读取的时候, 使用ThreadLocal包装, 能将线程和数据库连接绑定, 方便事务操作.

转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 zhao4xi@126.com

文章标题:ThreadLocal使用场景及内存泄漏

文章字数:863

本文作者:Zhaoxi

发布时间:2018-12-23, 15:03:24

最后更新:2019-09-21, 15:24:44

原始链接:http://zhao4xi.github.io/2018/12/23/ThreadLocal使用场景及内存泄漏/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录