ThreadLocal总结
初识ThreadLocal
1 ThreadLocalMap、ThreadLocal、Thread 的关系
1、Thread 中有个成员变量 threadLocals 类型为 ThreadLocalMap
ThreadLocalMap.ThreadLocalMap threadLocals = null;
2、ThreadLocalMap 是ThreadLocal的内部类
3、ThreadLocalMap key 为ThreadLocal,value为存储的对象
当我们去访问一个ThreadLocal 变量,其实就是从这个Thread 获取ThreadLocalMap,再从ThreadLocalMap中找到这个ThreadLocal。
2 ThreadLocal 简单使用
public class ThreadLocalTest {
//新建两个 ThreadLocal
static ThreadLocal<String> localVar1 = new ThreadLocal<>();
static ThreadLocal<String> localVar2 = new ThreadLocal<>();
static void print(String str) {
System.out.println(str + " :" + localVar.get());
localVar1.remove();
}
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
localVar1.set("localVar1");
localVar2.set("localVar2");
print("thread1");
//打印本地变量
System.out.println("after remove : " + localVar1.get());
System.out.println("after remove : " + localVar2.get());
});
Thread t2 = new Thread(() -> {
localVar1.set("localVar2");
print("thread2");
System.out.println("after remove : " + localVar1.get());
System.out.println("after remove : " + localVar2.get());
});
t1.start();
t2.start();
}
}
结果输出
thread1 :localVar2
after remove : null
after remove : localVar2
thread2 :localVar2
thread2 :null
after remove : null
after remove : null
3 set 方法解析
public class ThreadLocal<T> {
//........
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的成员变量 ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 首次set(localVar1.set()) 时 map为空 此时会通过createMap()创建ThreadLocalMap
if (map != null)
// 当map不为空时 直接将ThreadLocal放进去 此处 this=localVar2 value="localVar2"
map.set(this, value);
else
// 首次set(localVar1.set()) 时 map为空 此时会通过createMap()创建ThreadLocalMap
// 此处也是ThreadLocalMap、ThreadLocal第一次和Thread绑定
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
// 执行ThreadLocalMap的构造方法 初始化一个ThreadLocalMap 并赋值给当前线程 此时 this =localVar1 firstValue="localVar1"
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
}
4 get方法
public class ThreadLocal<T> {
// ...
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap return t.threadLocals;
ThreadLocalMap map = getMap(t);
// 如果此线程的ThreadLocalMap还没创建 则调用初始化方法 setInitialValue
if (map != null) {
// ThreadLocalMap 其实就一个Entry数组 类似于HashMap中有一个Entry数组 Entry为key-value格式 key为当前threadLocal value为实际存的值(示例中的:key=localVar2 value="localVar2")
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 如果此线程的ThreadLocalMap还没创建 则调用初始化方法 setInitialValue
return setInitialValue();
}
// ...
private T setInitialValue() {
// get方法无value 需要初始化一个vaule==null 这也就是为什么从一个为初始化的map中get的值为null
T value = initialValue();
// 获取当前线程
Thread t = Thread.currentThread();
// 后面的操作和set方法一样了
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
}
5 为什么要把ThreadLocalMap设置成ThreadLocal的内部类
ThreadLocalMap是一个线程本地的值,其中的所有方法都是 private的,将ThreadLocalMap设置成ThreadLocal的内部类,这样就只有ThreadLocal这个类才能访问,对其他类是透明的,对其他ThreadLocal也是透明的。
6 内存泄露问题
当线程从一个栈帧中出来(threadLocal如果为局部变量)时或者主动将threadLocal设置为null,此时堆中的 threadLocal实例就只有ThreadLocalMap中的key指向,但是ThreadLocalMap中的key 是若引用,因此key指向的实例将会被回收,但是vlaue不会被回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。
所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。最好的做法是将调用threadlocal的remove方法.
再次认识ThreadLocal
先看例子
public class TestThreadLocal implements Runnable {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("主线程");
System.out.println("----主线程设置后获取值:" + threadLocal.get());//主线程
Thread tt = new Thread(new TestThreadLocal());
tt.start();
}
@Override
public void run() {
System.out.println("----子线程设置值前获取:" + threadLocal.get());//null
threadLocal.set("子线程");
System.out.println("----新开的线程设置值后获取:" + threadLocal.get());//子线程
}
}
即ThreadLocal无法在父子线程中传递,怎么解决呢? 使用jdk的 InheritableThreadLocal
再看代码
public class TestInheritableThreadLocal implements Runnable {
private static InheritableThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("主线程");
System.out.println("----主线程设置后获取值:" + threadLocal.get());//主线程
Thread tt = new Thread(new TestInheritableThreadLocal());
tt.start();
}
@Override
public void run() {
System.out.println("----子线程设置值前获取:" + threadLocal.get());//主线程
threadLocal.set("子线程");
System.out.println("----新开的线程设置值后获取:" + threadLocal.get());//子线程
}
}
在子线程设置值之前,能够get到主线程设置的值了,说明在父子进制之间传递了。子线程修改不影响主线程。
InheritableThreadLocal 原理:
InheritableThreadLocal 和theadLocal一样。Thread 中有个成员变量 threadLocals 类型为 ThreadLocalMap
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = nulll;
所以InheritableThreadLocal 和ThreadLocal 原理上是一样的。
再看下父子线程传递的原理,看源码,看thread的源码
当我们在new Thread时,首先会检查父线程的inheritableThreadLocals是否为空,如果不为空,则将父线程的inheritableThreadLocals复制过来。这就是父子线程传递的原理,同时子线程的修改不会影响父线程的数值。
//构造方法
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
// 初始化操作
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc) {
// ......
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID */
tid = nextThreadID();
}
父子线程传递的原理是在子线程构造方法中实现的,因此对于线程池来说,线程只会被新建一次,因此inheritableThreadLocals不适用线程池(具体的现象可以亲自动手操作下)
如果用线程池,怎么包成线程之间数据传递呢? 解决方案是阿里巴巴开源框架TTL: transmittable-thread-local
看示例:
先引包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>transmittable-thread-local</artifactId>
</dependency>
public static void main(String[] args) {
TransmittableThreadLocal<String> transmittableThreadLocal = new TransmittableThreadLocal<>();
// 验证线程池传递2
ExecutorService executorService2 = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));
for (int i = 0; i < 10; i++) {
transmittableThreadLocal.set("transmittableThreadLocal-value->" + i);
executorService2.submit(() -> {
System.out.println(transmittableThreadLocal.get());
});
}
executorService.shutdown();
inheritableThreadLocal.remove();
transmittableThreadLocal.remove();
}
结果:
transmittableThreadLocal-value->1
transmittableThreadLocal-value->2
transmittableThreadLocal-value->3
transmittableThreadLocal-value->4
本文详细介绍了ThreadLocal的工作原理,包括ThreadLocal、ThreadLocalMap与Thread之间的关系,以及set和get方法的实现。通过示例展示了ThreadLocal在不同线程中的独立性,并探讨了ThreadLocal可能导致的内存泄漏问题及其解决方案。最后,提到了InheritableThreadLocal用于线程间值传递的特性,并指出在线程池场景下,如何利用TTL库实现跨线程的数据传递。
11万+

被折叠的 条评论
为什么被折叠?



