之前在看源码的时候,忽略了这个知识点,但是在后面看到框架底层源码的时候,看到spring的事务有用到这个点,所以就再回头学习下threadLocal的原理

首先threadLocal是线程本地变量的意思,主要用来做线程数据隔离的操作,在使用的过程中,我们主需要关注threadLocal就可以,但是在底层源码层面,需要设计到几个相关的知识:
thread、threadLocal、threadLocalMap三者之间的关系
1、thread对象持有了一个threadLocalMap,但是对threadLocalMap的set、get、remove都是通过threadLocal来进行操作的
2、threadLocalMap的key是threadLocal对象的指针,value是我们程序员要设置的值

弱引用

我们通常说threadLocalMap是弱引用,如何体现?

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

这是threadLocalMap的entry对象,可以看到,是继承了weakReference,是弱引用

set

在要做线程数据隔离的时候,通常我们调用的是threadLocal.set()方法,来看下源码

/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** 这是设置当前线程绑定的局部变量* 1.获取到当前线程* 2.根据线程,获取到thread中所维护的threadLocalMap* 3.如果map不为null,就将当前threadLocal作为key,设置到线程维护的threadLocalMap中* 4.如果map为null,就创建一个新的threadLocalMap,并将key和value赋值进去* @param value the value to be stored in the current thread's copy of*        this thread-local.*/public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);}

这里的getMap(t),就是调用thread中的threadLocalMap
set(this,value):调用的是threadLocalMap的set方法,关于map的set方法后面单独介绍

get

/*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local* 这里应该是根据当前线程从map中获取到当前线程对应的value*/public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}

这里可以看到,在调用get()方法的时候,会根据当前线程从thread中获取到对应的threadLocalMap,我觉得这里就是为什么可以隔离的原因,不同的thread,获取到的是不同的threadLocalMap,如果当前线程对应的entry不为null,就返回对应的value

threadLocalMap.set()

接着来看下threadLocalMap的set方法是如何存入一个元素的

/*** Set the value associated with key.* 这里是向集合中插入元素的源码,这里和hashmap是不同的处理思想* @param key the thread local object* @param value the value to be set*/
private void set(ThreadLocal<?> key, Object value) {// We don't use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;/*** 1.这里的threadLocalHashCode是根据atomicInteger来获取值的* 每调用一次set方法,atomicInteger ++;*/int i = key.threadLocalHashCode & (len-1);/*** 2.判断i位置是否已经存在元素* 如果i != null,就通过nextIndex获取到i+1位置的元素,需要注意的是:如果 i+1 > len,就会从0开始*/for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();/*** 2.1 如果key相同,直接覆盖*/if (k == key) {e.value = value;return;}/*** 2.2 如果i位置的key为null,就直接用当前key和value,写入到i位置* 这里就和弱引用有点关系了;* 如果key是弱引用,在使用完之后,如果我们没有调用threadLocal.remove()方法,key会被回收,因为key是弱引用* 但是value不会被回收,此时就出现了key为null,但是value依旧有值,如果是这种情况,在源码中就可以直接进行覆盖** 但是反之,如果我们key使用的是强引用,即使使用完了,key也不会被回收,依旧不是null,在不是null的情况下,这里如何判断key是已经使用完了?还是没有使用完?* 所以:对于使用弱引用来说,即使我们没有调用remove方法,在一定程度上,可以通过key为null,value不为null这个场景来减少内存溢出的场景*/if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;/*** 3.cleanSomeSlots是清除那些e.get() == null的元素* 如果清除失败,且数组长度超过阈值,就扩容*/if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}

这里threadLocalMap在处理冲突的时候,采用的是开放寻址法,如果i位置冲突,就去i+1位置判断,是否可以放入到,如果i+1 大于数组长度,那就从0位置继续开始

threadLocalMap和hashmap

其实整个threadLocalMap的源码看下来,和hashmap是有很大的区别的
1.threadLocalMap的key是threadLocal对象
2.threadLocalMap的hash值的计算,是根据atomicInteger来计算的
3.在处理冲突的时候,threadLocalMap采用开放寻址法,hashmap采用链地址法

内存溢出问题

threadLocal如果使用不当会有内存溢出问题,既然threadLocal的key采用的是弱引用,为什么还会有内存溢出?因为key虽然是弱引用,但是value是强引用,在使用完了之后,key(threadLocal)会被回收,但是value依旧存在,所以会存在无法回收的场景
那如果map的key采用强引用,也会导致无法回收,但是如果使用弱引用,会有一个好处:
在往map中插入元素的时候,如果i位置的key不存在,但是value存在,那此时就会认为该位置可以进行覆盖,因为key为null,value不为null,就是被回收的场景,只是value是强引用,无法被回收
但是如果key和value都是强引用的话,那在set的时候,就无法区分key是使用完了,还是未使用完,所以:key使用弱引用,可以在一定程度避免内存溢出问题
所以,最好是,在使用完,就调用threadLocal的remove方法,将key和value从map中移除

总结

所以,总结而言:
1、对于threadLocal的使用,最好是在使用完了之后,就手动的remove
如果不remove,那由于threadLocalMap的特性,在使用完之后,会对key进行释放,在下次进行插入的时候,会对该位置进行覆盖,只是在一定程度上,可以避免内存溢出

2、threadLocalMap在冲突的时候,采用的是开放寻址法,key的hash值,是通过一个atomicInteger变量来计算的

ThreadLocal源码学习相关推荐

  1. JDK7 源码学习系列——ThreadLocal

    为什么80%的码农都做不了架构师?>>>    学习JDK中的类,首先看下JDK API对此类的描述,描述如下: 该类提供了线程局部 (thread-local) 变量.这些变量不同 ...

  2. Java8 ThreadLocal 源码分析

    可参考文章: Java8 IdentityhashMap 源码分析 IdentityhashMap 与 ThreadLocalMap 一样都是采用线性探测法解决哈希冲突,有兴趣的可以先了解下 Iden ...

  3. ThreadLocal源码分析-黄金分割数的使用

    前提# 最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量.问题是解决了,但是后来发现对 ...

  4. Mybatis源码学习(三)SqlSession详解

    前言 上一章节我们学习了SqlSessionFactory的源码,SqlSessionFactory中的方法都是围绕着SqlSession来的.,那么SqlSession又是什么东东呢?这一章节我们就 ...

  5. hystrix 源码 线程池隔离_Spring Cloud Hystrix 源码学习合集

    # Spring Cloud Hystrix 源码学习合集 **Hystrix: Latency and Fault Tolerance for Distributed Systems** ![](h ...

  6. Shiro源码学习之二

    接上一篇 Shiro源码学习之一 3.subject.login 进入login public void login(AuthenticationToken token) throws Authent ...

  7. Shiro源码学习之一

    一.最基本的使用 1.Maven依赖 <dependency><groupId>org.apache.shiro</groupId><artifactId&g ...

  8. mutations vuex 调用_Vuex源码学习(六)action和mutation如何被调用的(前置准备篇)...

    前言 Vuex源码系列不知不觉已经到了第六篇.前置的五篇分别如下: 长篇连载:Vuex源码学习(一)功能梳理 长篇连载:Vuex源码学习(二)脉络梳理 作为一个Web前端,你知道Vuex的instal ...

  9. vue实例没有挂载到html上,vue 源码学习 - 实例挂载

    前言 在学习vue源码之前需要先了解源码目录设计(了解各个模块的功能)丶Flow语法. src ├── compiler # 把模板解析成 ast 语法树,ast 语法树优化,代码生成等功能. ├── ...

  10. 2021-03-19Tomcat源码学习--WebAppClassLoader类加载机制

    Tomcat源码学习--WebAppClassLoader类加载机制 在WebappClassLoaderBase中重写了ClassLoader的loadClass方法,在这个实现方法中我们可以一窥t ...

最新文章

  1. CentOS-6.5安装配置Tengine
  2. access手工注入笔记
  3. 30个Python常用极简代码
  4. VTK:可视化之BillboardTextActor3D
  5. 【转载】广告系统架构解密
  6. Android 使用Fragment,ViewPagerIndicator 制作csdn app主要框架
  7. 二次解析视屏 php,最新PHP二次视频解析源码 带后台版
  8. VM虚拟机 自定义(高级)安装win 7系统史上超详细图文教程(附win7 iso镜像资源)
  9. 这些年,阿里巴巴技术大牛们曾经写过哪些书籍!
  10. 正点原子STM32(基于HAL库)2
  11. 渗透测试技巧:python+burp快速编写网站测试脚本
  12. 通过学习RN技术,平常自己的一些笔记整理,希望借鉴
  13. 如何在 Windows 下创建 macOS 引导介质 (USB 启动盘)
  14. Never give up
  15. Elasticsearch集成(二)
  16. 视觉定位系统怎么实现定位及引导贴合的应用?视觉定位系统案例详解
  17. 大数据评估TMT公司内在价值
  18. 走入Vue 2.0-姜威-专题视频课程
  19. 前端案例 ——注册页面(html+css实现)
  20. Python学会这一招让你穷游全世界(驴友必备)

热门文章

  1. 阿里云云计算 8 ECS的实例规格
  2. 算法:动态规划 最大连续子数组和 Maximum Subarray
  3. NumPy库—random模块
  4. 433.最小基因变化
  5. 116.填充同一层的兄弟节点
  6. Matplotlib库使用总结
  7. 多小区下小区上行速率的计算(4)
  8. KNN(k-nearest neighbor algorithm)--从原理到实现
  9. python中关于图例legend在图外的画法简析
  10. java基础学习(3)