类是Java提供的用来保存线程本地变量的机制。说道线程本地变量很容易和和线程栈帧里的本地变量表联系起来。不过ThreadLocal的最普遍的用途是避免线程安全问题和框架代码实现模板模式。说道线程安全又要温习一下多线程知识了。线程安全就是多线程访问下程序的不变约束、后验条件等不被破坏程序保持正确性,原子性、可见性、重排序等情况更靠近使用层,Java中引起并发问题的最基本的是共享可变变量。所以避免线程安全问题有几种思路
- java.lang.ThreadLocal<T>
不同享的一种方式就是使用线程私有变量,例如方法中创建的对象只要没有泄露都在本线程栈的引用上,其他线程无法引用到,而一个线程的内执行是线程安全的。
ThreadLocal变量可以让每个线程都拥有自己私有的变量而不会互相访问到,从而实现线程安全。另外一些框架比如spring-jdbc,因为java.sql.Connection不是线程安全的,会将jdbc的Connection保存在ThreadLocal中,然后在框架层负责Connection的获取、使用、释放等操作,将底层的细节向用户屏蔽,当然这是基于Javaweb服务通常都是一链接一线程的前提下。另外一些服务跟踪代码也可以利用ThreadLocal获取调用信息,这样就能把分散的跟踪日志绑定到一起。
首先看一下ThreadLocal的相关用法。
|
|
可以看到还是比较简单的,一般的使用方式是声明一个唯一的ThreadLocal变量(一般是静态变量,原因后面会讲到),如果override了initValue或使用withInitial方法创建的ThreadLocal,则ThreadLocal会在第一次get时调用初始化方法(之前没有set过的情况), 如果调用了remove,后面的第一个get也会初始化。
简单使用示例。下面以一个写读的例子说明如果让一个线程不安全。
name2 这个全局变量,虽然String本身是不可变的,但在当前使用场景下是先写再读取,而这两个操作中间是有可能被其他线程修改的。而name这个ThreadLocal变量就不会有其他线程的干扰。
|
|
对于ThreadLocal的实现方式,我们可能会想到使用Map
简单点说,每个Thread会有一个ThreadLocalMap,ThreadLocalMap的Entry是一个WeakReference
|
|
ThreadLocal中的get、set方法,均是先获取到当前线程的ThreadLocalMap,再找对应的ThreadLocal的Entry再找到对应的值的。所以一种错误的使用方式每次调用创建新的ThreadLocal变量。
|
|
ThreadLocalMap中的Entry继承自WeakReference,所以当所有引用一个ThreadLocal变量的都是WeakReference时,GC收集器会将其gc掉。但是一般都会将TheadLocal声明为静态变量,总是有Class对象对其的引用,所以只要线程存活,ThreadLocalMap中的对应的Entry的Value总是存活也就是reachable的。为了防止这个问题可能造成的内存泄露,要记得在整个线程流程执行完成时调用
;WeakReference属于Reference之一,除了平常直接使用的强引用,还有SoftReference、WeakReference、PhatomReference,强度依次降低,SoftReference会在OOM前被清除,WeakReference则在GC前会被递归清楚来达到释放内存的目的。
- threadLocal.remove()
|
|
在一些代码中,可能会使用ThreadLocal作为隐式传参的方法,这样会在不同模块的代码间加上隐含的耦合,容易出错。
来源: https://liuzhengyang.github.io/2017/11/02/thread-local/