ThreadLocal从简单使用及源码

是什么?

直译是“线程本地(变量)”,一个类似map结构的类,为每个线程变量单独创建一个副本,保证线程安全

为什么要用?

理由1:跨越多个方法进行参数传递
public class School {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        say(1);
    }

    static void say(int time) {
        threadLocal.set(time+"");
        talk();
    }

    static void talk(){
        run();
    }

    static void run(){
        System.out.println("从ThreadLocal拿出来的:" + threadLocal.get());
    }

}

在这里插入图片描述
借助这一点,我们可以不在方法上显示声明一个传参,也可以进行某个参数传递,但是这样用得很少,首先它只能放一个参数(也可以存一个HashMap之类的进去,但是数据结构一复杂,更加难维护,所以强烈不建议用来传参),如果这时候我再set一遍2,1就会被覆盖;其次这样极其不容易维护,调用链过长的话,根本不知道该参数从哪里传下来的,代表什么

理由2:多线程环境下保证数据安全

例子:假设循环创建1000个线程,每个线程都用当前次循环的i值去调用say方法,方法中把对应参数值赋给一个静态变量num,然后还让线程sleep1毫秒模拟执行复杂业务逻辑,然后再输出num,同时往threadlocal中放入一开始的num。

public class School {

    static ThreadLocal<String> threadLocal = new ThreadLocal<>();

    static int num ;

    static void say(int time) throws InterruptedException {
        num = time;
        threadLocal.set(num+"");
        Thread.sleep(1);
        System.out.println("从ThreadLocal拿出来的:" + threadLocal.get());
        System.out.println("从当前方法的静态变量中的:"+num);
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            // 线程创建
            int finalI = i;
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        say(finalI);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }
    }
}

查看结果:
在这里插入图片描述
不难看出从当前方法的静态变量中拿出来打印的很多重复值,发生了典型的值覆盖的线程安全问题,而从ThreadLocal中取出来的就是保证了没有重复值,这就是其中一个应用场景,典型的这个线程不安全的场景就有大名鼎鼎的日期格式化工具simpledateformat,多线程传进去的time会被覆盖

注意⚠️
使用完的值,必须要remove掉,不然内存会一直被占用
理由3:可以保存对应线程的事务信息,数据库连接,会话管理等

这个可以看Spring管理数据库连接的源码或者网上其他SqlConnection的例子,就不举例了,背八股一样背下来吧

它是怎么实现这一套流程的呢?

四个比较重要的概念

Thread, ThreadLocals,ThreadLocal,ThreadLocalMap
首先Thread是线程,ThreadLocals是Thread里面的一个变量,也就是存储当前线程的变量副本的地方,很多人都知道变量通过ThreadLocal存入,会误以为就存在ThreadLocal里面的Map里的Value里面,其实是存在对应的Thread线程里
在这里插入图片描述
ThreadLocalMap是ThreadLocal里面的一个内部类,从命名上我们就能看出是Map结构
在这里插入图片描述

总结:Thread里面有个ThreadLocalMap类型的ThreadLocals,可通过当前Thread的ThreadLocal引用去存储和查询对应的那一个Key Value, ThreadLocal里面有ThreadLocalMap内部类,仅仅是定义了这么一个结构体,在使用ThreadLocal的get, set,remove方法时是往对应Thread的ThreadLocals里面添加/查询/移除 线程本地变量Value(有点绕,但是面试会问)

在这里插入图片描述
记住就是,ThreadLocal本身只维护一个映射关系,不保存数据,对应的数据都保存在线程本身

三个比较重要的方法

(1)Get

在这里插入图片描述
返回当前线程副本中的值
线程局部变量。如果变量对于
当前线程,它首先初始化为返回的值
通过调用{@link#initialValue}方法。
获取当前线程,通过当前线程去获取map,然后通过this当前ThreadLocal引用去获取值,做一下判断然后返回,如果map空,把初始值null放入当前Thread的ThreadLocals中,返回一个初始值null
在这里插入图片描述
在这里插入图片描述

(2) Set

在这里插入图片描述
设置此线程局部变量的当前线程副本
*设置为指定的值。大多数子类都不需要这样做
*重写此方法,仅依赖{@link#initialValue}
*方法来设置线程局部变量的值。
首先看到它获取当前线程Thread t , 然后根据当前线程通过getMap方法获取ThreadLocalMap,这里返回的就是threadLocals变量
在这里插入图片描述
做了一下判空,然后往map里面设value,注意设置的key值this是当前的ThreadLocal引用,这样就把一个Thread和ThreadLocal绑起来了

(3) remove

在这里插入图片描述
删除此线程的当前线程值
*变量。如果该线程局部变量
*{@linkplain#get read}被当前线程读取,其值将为
*通过调用{@link#initialValue}方法重新初始化,
*除非它的值是当前线程设置的{@linkplain#set}
*在此期间。这可能会导致多次调用
*当前线程中的{@code initialValue}方法。
通过当前线程去拿Map,调用m.remove方法,删除对应Key(也就是this当前ThreadLocal引用)的Value
它底层还有一堆数组下标移动,槽位计算,rehash的算法逻辑,那些在这里不展开,有兴趣或者真的有用再深入研究

其余知识点须知

ThreadLocalMap与普通HashMap不同

我们都知道HashMap通过数组+链表+红黑树完成数据的存放,但是ThreadLocalMap只是一个简单的数组,该怎么解决hash key冲突问题
隆重介绍线性探索法,其实很简单,就是计算出对应的hash key,如果指定位置有值存放,那么再通过固定的算法计算下一个存放位置,其实就是+1-1
在这里插入图片描述
在这里插入图片描述

内存泄漏

1:没用的ThreadLocals不remove,线程不消亡,导致一直被占用
2:Map对ThreadLocal引用是弱引用,每次GC都会被回收。而ThreadLocal引用是作为Key的存在,所以当Key被回收掉了,Value自然没办法再被访问,从而泄漏内存,所以还是要记得用完就remove
在这里插入图片描述
关于强弱虚引用区别可以看另外一篇:强弱虚引用的区别

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值