ThreadLocal总结

本文详细介绍了ThreadLocal的工作原理,包括ThreadLocal、ThreadLocalMap与Thread之间的关系,以及set和get方法的实现。通过示例展示了ThreadLocal在不同线程中的独立性,并探讨了ThreadLocal可能导致的内存泄漏问题及其解决方案。最后,提到了InheritableThreadLocal用于线程间值传递的特性,并指出在线程池场景下,如何利用TTL库实现跨线程的数据传递。

初识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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值