Java并发编程:ThreadLocal

什么是ThreadLocal?它有什么作用?

ThreadLocal被称为线程局部变量,每个线程可以创建属于其独享的对象,一个线程不能访问另外一个对象创建的实例,达到里线程隔离效果。

ThreadLocal相当于线程访问其线程特有对象的代理(Proxy),就是说每个线程都可以通过其创建并访问各自线程特有对象,泛型T指定了相应线程特有对象的类型。

一个线程可以使用不同的ThreadLocal实例来创建并访问其不同的线程特有对象多个线程使用同一个ThreadLocal实例所访问到的对象是类型T的不同实例,即这些线程持有各自的线程特有对象实例。

Threadlocal的一个非常大的作用在于,既能够在多个方法中共用一个变量,而这个变量又和其他线程隔离

ThreadLocal的使用场景

1.隐式传参(Implicit Parameter Passing)。在业务开发中,如果一个参数使用到的地方非常多,在各个类中需要传递,我们通常使用方法传参,这时候使用ThreadLocal就不需要传递了,在一条线程里可以随时用到。不过,也有的观点认为隐式传参使得系统难以理解。隐式传参的实现方式通常是使用一个(工具)类的静态方法来封装对线程特有对象的访问,相应的地方只需要调用静态方法就可以设置或者获线程特有对象。

2.使用线程安全对象或者非线程安全对象,不希望引入锁或者内部锁的使用带来资源开销问题。

3.特定于线程的单例(Singleton)模式。广泛使用的单例模式所实现的效果是在一个Java虚拟机中的一个类加载器下某个类有且只有一个实例。如果我们希望对某个类每个线程有且只有一个该类的实例,就可以用线程独有对象。

容易入坑?内存泄漏?

内存泄漏(Memory Leak)指由于对象永远无法被垃圾回收导致其占用Java虚拟机内存无法被释放,持续的内存占用会导致Java虚拟机可用内存逐渐减少,并最终可能导致Java虚拟机内存溢出(Out Of Memory),直到Java虚拟机宕机。


强引用 > 软引用 > 弱引用 > 虚引用

在Web应用中使用ThreadLocal中如果处理不当极易导致内存泄漏。先简单了解一下ThreadLocal的内部实现机制。ThreadLocalMap是Thread的成员变量,ThreadLocal的静态内部类,类似HashMap结构,每个ThreadLocalMap内部都会含有若干个Entry条目(就是Key-Value键值对),Entry的key为ThreadLocal实例,value是线程特有对象

由于Entry对ThreadLocal实例的引用是一个弱引用(WeakReference),因此它不会阻止被引用的ThreadLocal实例被垃圾回收,Entry的key可能为null,这时候的Entry被称为无效条目(Stale Entry)。在value方面,Entry对其的引用为强引用,那么无效条目会阻止其引用的线程特有对象被垃圾回收。由于这个原因,当ThreadLoaclMap有新的Entry条目被添加进去的时候,ThreadLocalMap会把无效条目清理掉(ThreadLocalMap在创建新的映射关系的时候可以复用无效条目,不一定要创建新的条目)。

但是,这个处理有一定缺陷。当有无效条目积累但是线程处于非运行状态的时候,该线程的ThreadLocalMap不会有任何变化,这可能会导致各个线程的各个Entry所引用的线程特有对象都无法被垃圾回收,导致了内存溢出。

如何规避内存泄漏

项目中通常执行Threadlocal.remove()操作来规避内存泄漏,在以下场景:

  1. Web服务器对于同一个Http请求进行处理的时候,Filter.doFilter方法的执行线程和Servlet的执行线程是相同的,所以可以自定义个过滤器来,在doFilter方法执行完后执行

  2. 使用try-finally保证执行

  3. 在拦截器中的afterCompletion方法中执行

子线程能获取父线程的ThreadLocal值吗

假如有一个场景,子线程需要需要使用存放在threadLocal变量里的用户信息(用户id、用户姓名或者用户类型等),再比如一些中间件需要把统一的id追踪的整个调用链路记录下来,会使用什么方式实现?

  1. 可以在父线程创建子线程的时候传入父线程的变量
  2. 使用InheritableThreadLocal,继承自ThreadLocal,提供了一个很重要的特性,就是让子线程可以访问在父线程中设置的本地变量

源码实现

主要几个方法:set、get、remove、setInitialValue

1. get & set

通过获取当前线程来获取ThreadLocalMap,通过传入this(注意这里是ThreadLocal实例而非Thread)获取Entry条目,若不为空,直接返回T,否则直接返回setInitialValue方法返回的value.

在getMap方法中,直接返回线程t的成员变量threadLocals

Thread类里:

ThreadLocal的静态内部类,可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键

接下来看到setInitialValue()方法,initialValue()为protected类型,给子类重写的。后面是同样的,也是ThreadLocalMap不为空,设置键值对,否则创建Map

createMap创建一个ThreadLocalMap给线程的threadLocals

set()方法类似,不再赘述

至此,我们知道了每个线程内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threaLocals,用来存储线程特有对象,键为ThreadLoca变量,值为线程特有对象。初始化时,如果调用get或者set方法,就会对Thread.ThreadLocalMap进行初始化

posted @ 2020-05-16 12:18  Kobelieve  阅读(362)  评论(0编辑  收藏  举报