Java-ThreadLocal

1. 简介

主要解决多线程中数据因并发产生不⼀致问题,同个线程共享数据,ThreadLocal为每⼀个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同⼀个对象,这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

ThreadLocal不能使用原子类型,只能使用Object类型

  • 原理和源码
    • ThreadLocal中的⼀个内部类ThreadLocalMap,这个类没有实现map接口,就是⼀个普通的Java类,但是实现的类似map的功能
    • 每个数据用Entry保存,其中的Entry继承与WeakReference,用⼀个键值对存储,键为ThreadLocal的引用。
    • 每个线程持有⼀个ThreadLocalMap对象,每⼀个新的线程Thread都会实例化⼀个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals,则直接使用已经存在的对象。

2. 3种实现

2.1 ThreadLocal

2.2 InheritableThreadLocal

InheritableThreadLocal继承于ThreadLocal,主要解决父子线程变量传递问题,InheritableThreadLocal可以把父线程的变量传递给子线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class InheritableThreadLocal<T> extends ThreadLocal<T> {

protected T childValue(T parentValue) {
return parentValue;
}

ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}

void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}

public class ThreadLocal<T> {
...
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
...
}

// 在初始化的时候,进行传值
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  • 原理

    • InheritableThreadLocal存储数据的在ThreadLocalMap的inheritableThreadLocals对象里,在线程初始化的时候,该对象会复制父线程的数据到子线程
    • ThreadLocalMap存储数据在threadLocals中,不会复制父线程数据
  • 存在的问题

    • 使用线程池时失效,InheritableThreadLocal是在创建初始化线程的时候,进行赋值,但是线程池中的线程都是提前创建好的,所以无法复制线程变量

2.3 TransmittableThreadLocal

在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。

3. 问题

  • 为什么ThreadLocal的键是弱引用,如果是强引用有什么问题

    • Java中除了基础的数据类型以外,其它的都为引用类型。而Java根据其生命周期的长短将引用类型又分为强引用 、 软引用 、 弱引用 、 虚引用,正常情况下我们平时基本上我们只用到强引用类型

    • 强引用 new了⼀个对象就是强引用 Object obj = new Object();

    • 软引用的生命周期比强引用短⼀些,通过SoftReference类实现,当内存空间足够,垃圾回收器就不会回收它; 当JVM认为内存空间不足时,就会去试图回收软引用指向的对象,也就是说在JVM抛出OutOfMemoryError之前,会去清理软引用对象主要用来描述⼀些【有用但并不是必需】的对象
      使用场景:适合用来实现缓存,内存空间充足的时候将数据缓存在内存中,如果空间不足了就将其回收掉

    • 弱引用是通过WeakReference类实现的,它的生命周期比软引用还要短,在GC的时候,不管内存空间足不足都会回收这个对象
      使用场景:⼀个对象只是偶尔使用,希望在使用时能随时获取,但也不想影响对该对象的垃圾收集,则可以考虑使用弱引用来指向该对象。

    • 如果是强引用,即使把ThreadLocal设置为null,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏

    • 如果是弱引用,引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下⼀次ThreadLocalMap调用set、get、remove的时候会被清除。