threadlocal源代码阅读,及深度思考

Threadlocal

初始化过程

  1. Thread类有个变量,ThreadLocal.ThreadLocalMap threadLocals = null;
  2. 每次set的时候,(线程内部的同一个threadlocalMap)主线程的 threadlocals变量保存内容,key就是threadlocal
  3. 底层是一个entry数组,数组元素的keythreadlocalentry继承weakReference,备注:基本不会用到数组的情况,因为一般使用都是一个threadlocal,除非出现多个threadlocal
  4. Threadlocal底层是一个volatile的引用队列,volatile ReferenceQueue<? super T> queue;

Threadlocal有什么用?

提供内部的局部变量,这种变量在多线程环境下访问时候可以保证各个线程内的变量相对独立与其他线程内的变量,threadlocal实例通常来说都是private static的,用于关联线程和上下文的

作用:提供线程内的局部变量,不同的线程之间不会相互干扰,这个变量在线程的生命周期中起作用,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度

一般我们会重写initialValue方法来重新赋值

Threadlocal- Jdk1.8以后

每个线程对应的Thread对象内部都有一个(Threadlocals字段- ThreadLocal.ThreadLocalMap threadLocals),这个字段会指向堆中的一个ThreadlocalMap(存储的是当前线程与其他的Threadlocal对象关联的数据)

通俗讲

Thread对象,里面保存了一个ThreadLocalMap对象,map里面保存的是当前线程跟这个对应的Threadlocal对象关联的数据

如何做到线程相互不干扰的?

调用当前线程主线程的thread里面,有个ThreadLocal.ThreadLocalMap所有的子线程都保存在这一个threadlocalMap里面,key就是每个线程产生一个this(即threadlocal

参考:threadlocalgetset方法

Threadlocal的hashcode怎么重写的

用一个黄金分割数来分割的,这样就会均匀分布在entry数组里面,

举例子,比如数组长度是16,table 0 ,4,8,16这样比较均匀

为什么Threadlocal自定义了map,没有使用hashmap

1.自己重写的key的类型可以定义,key用的是弱引用,hashmap的key是强引用,弱引用是不影响对象回收的

2.Threadlocalmap它的这个写数据和查数据过程中,它有这个过期清理数据的功能,从某种意义上解决了内存泄露问题,如果Threadlocalkey过期不干掉,会出现问题

清理数据的方法:rehash

 

每个线程的Threadlocalmap是什么时候创建的

第一次调用threadlocal的set方法的时候,会对ThreadLocalMap
进行判断是否为空,如果为空,则创建ThreadLocalMap

 

Map底层数组初始化容量?为什么

16,因为方便hash寻址,因为2的次方数减一之后一定是一堆1组成的二进制数,如果数值与这种二进制数进行按位与运算,得到的数一定是大于等于0或者小于等于这个二进制数值的,比使用取模算法(%)效率高多了

阈值是多少,是否会直接扩容

1. 阈值是当前数组的2/3,但不会直接扩容。

2. 会refresh一次,先进行一个全表扫描,把过期数据给清理掉,重新整理这个散列表,如果仍然可以达到这个3/4的话,就真正的扩容

 

Threadlocalmap对象的get逻辑

1. Get的时候拿到的是一个Threadlocal对象,根据这个Threadlocal对象的hash值按与当前数组长度-1,查询出来,

2. 当然查到的数据可能不是,说明写数据的时候发生了hash冲突,但是内部不是链表结构,3. Threadlocalmap采取的策略是hash冲突后,线性的向后找到一个合适的位置去写数据,所以get如果第一次没有查询到,直接向后迭代查询找到这个或者找打null结束

 

Threadlocalmap对象的set逻辑

1. 跟key找到对应的index,如果slot为null,代表是一个新添加的数据

2. 如果不为null,

2.1 第一种情况插入

2.2 第二种情况,更新,

2.3 还有一种碰到过期数据了,待补充

 

inheritableThreadLocals

作用

InheritableThreadLocal的作用: 当我们需要在子线程中使用父线程中的值得时候我们就可以像使用ThreadLocal那样来使用InheritableThreadLocal

内部方法(共三个)

 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);

  }

Threadlocal思考

ThreadLocal用在什么地方?

  • 保存线程上下文信息,在任意需要的地方可以获取!
  • 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!
  1. 保存上下文信息举例子

1.1 常用的比如每个请求怎么把后续的请求关联起来,就可以用ThreadLocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。

1.2 Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。

2 线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失!!!

原因:

每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,所以ThreadLocal无法解决共享对象的更新问题!

阿里规范图片

 

SimpleDateformat使用demo

 

ThreadLocal一些细节!

对象图

 

弱引用

弱引用也是用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,该对象仅仅被弱引用关联,那么就会被回收。

ThreadLocal的最佳实践!

由于线程的生命周期很长,如果我们往ThreadLocal里面set了很大很大的Object对象,虽然set、get等等方法在特定的条件会调用进行额外的清理,但是ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是后续在也没有操作set、get等方法了。

所以最佳实践,应该在我们不使用的时候,主动调用remove方法进行清理。

最佳实践做法应该为:

代码

try { // 其它业务逻辑} finally{threadLocal对象.remove();}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值