关于ThreadLocal,ThreadLocalMap,Thread的关系

什么是ThreadLocal?

官方解释:ThreadLocal是一个本地线程副本变量工具类,主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰。
白话:ThreadLocal就是给每个线程储存值,且各个线程之间相互隔离。

ThreadLocal原理

get()方法

get()源码:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null)
            return (T)e.value;
    }
    return setInitialValue();
}

可以看出,它是通过获取当前线程,再通过当前线程获取当前线程管理的静态变量ThreadLocalMap,再通过key为ThreadLocal往map中取值。

set()方法

set()源码:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

获取当前线程,通过当前线程获取线程对应的ThreadLocalMap,再通过key为ThreadLocal往map中调用set方法。

ThreadLocal、Thread、ThreadLocalMap三者的关系

每一个Thread都对应一个ThreadLocalMap,该map在线程创建时已经创建出来,生命周期与Thread相同。ThreadLocalMap中存放Entry结点(<k,v>结构),每一个entry结点对应一个ThreaLocal。而ThreadLocal是Thread储存在ThreadLocalMap中的value的key,获取那一个结点。每一个ThreadLocal能够储存一个value,调用多次set方法会发送覆盖。
Hash冲突问题
因为根据ThreadLocal寻找entry结点这一过程涉及到hash算法,当得到的hash值发生冲突时(概率小),会采用线性探测寻找下一个位置的方法解决,效率很低。因此,建议每个线程只存一个变量(只有一个ThreadLocal)就不存在Hash冲突的问题。

内存泄漏问题

为什么会发送内存泄漏 ?
内存泄漏的定义:内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

从上面的分析可知,ThreadLocalMap的生命周期和Thread一样,那如果Thread是在线程池中,则是能够持续存活很长的一段时间的。有了这个前提,我们继续。而ThreadLocalMap中的key(也就是ThreadLocal)是一个弱引用,它在JVM下一次的GC中是会被回收的。而我们自己设置的存放在map中的value是一个强引用,JVM无法回收。如果key被回收了,我们也无法通过key来手动回收了,就发生了内存泄漏。得等到ThreadLocalMap被回收,也就是Thread被销毁。
如何解决?
①Java8中已经做了一些优化如,在ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。
②良好的编码习惯:加try,catch,finally限制。在try里面进行ThreadLocal.set(),在finally里面进行ThreadLocal.remove()手动删除entry。

ThreadLocal的应用例子

public class main {
    private static ThreadLocal<String> sThreadLocal = new ThreadLocal<>();
    public static void main(String args[]) {
        sThreadLocal.set("这是在主线程中");
        System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
        //线程a
        new Thread(new Runnable() {
            @Override
            public void run() {
                sThreadLocal.set("这是在线程a中");
                System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
            }
        }, "线程a").start();
        //线程b
        new Thread(new Runnable() {
            @Override
            public void run() {
                sThreadLocal.set("这是在线程b中");
                System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
            }
        }, "线程b").start();
        //线程c  
        new Thread(() -> {
            sThreadLocal.set("这是在线程c中");
            System.out.println("线程名字:" + Thread.currentThread().getName() + "---" + sThreadLocal.get());
        }, "线程c").start();
    }
}

输出:

线程名字:main---这是在主线程中
线程名字:线程b---这是在线程b中
线程名字:线程a---这是在线程a中
线程名字:线程c---这是在线程c中
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值