热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ThreadLocal的使用和实现原理

概述ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他

概述


ThreadLocal是一个将指定值储存到指定线程的类。简单的讲ThreadLocal可以指定储存数据类型,然后在不同线程中设置某个值,这个值在其他线程是不可以获取到的,只能在本线程才能获取到。平常中很少使用到ThreadLocal,但在也不少见到,比如Looper类中就有使用到它。


使用

ThreadLocal的使用不难,就是先创建ThreadLocal对象并指定要存储的类型,分别在各个线程中存放指定的值即可。

private ThreadLocal<String>mThreadLocal&#61;new ThreadLocal<>();//创建ThreadLocal对象并指定存储String类型mThreadLocal.set("main");//在主线程中存储"main"字符串Thread t1&#61;new Thread(new Runnable() {&#64;Overridepublic void run() {mThreadLocal.set("t1");//在t1线程中存储"t1"字符串Log.i(TAG, "t1-run:"&#43;mThreadLocal.get());}});Thread t2&#61;new Thread(new Runnable() {&#64;Overridepublic void run() {在t2线程中不存储任何值&#xff0c;直接获取Log.i(TAG, "t2-run:"&#43;mThreadLocal.get());}});t1.start();t2.start();Log.i(TAG, "main-run:"&#43;mThreadLocal.get());

最后日志输出的结果为&#xff1a;

com.sendi.threadlocaltest I/MainActivity: main-run:main
com.sendi.threadlocaltest I/MainActivity: t2-run:null
com.sendi.threadlocaltest I/MainActivity: t1-run:t1

根据输出的结果&#xff0c;很明显看出不同线程只能获取到各自存储的值&#xff0c;没有存储值则获取到null。它有点类似于HashMap&#xff0c;但实际内部实现却不同。
以下是它内部实现原理的解析。
在看源码之前&#xff0c;先上一张关系图
这里写图片描述

实现原理


  • 首先从它是set方法入手

public void set(T value) {Thread t &#61; Thread.currentThread();//获取当前线程ThreadLocalMap map &#61; getMap(t);//根据当前线程去获取到一个ThreadLocalMap对象if (map !&#61; null)map.set(this, value);//将value设置进ThreadLocalMap中elsecreateMap(t, value);//创建一个ThreadLocalMap对象&#xff0c;并将value存储到里边去}

createMap(Thread, T)方法源码如下&#xff1a;它创建一个ThreadLocalMap对象&#xff0c;并将该对象赋值给当前线程的threadLocals成员

void createMap(Thread t, T firstValue) {t.threadLocals &#61; new ThreadLocalMap(this, firstValue);}

getMap(Thread)方法比较简单&#xff0c;就是根据当前线程去获取到对应的ThreadLocalMap对象。

ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

接下来看看ThreadLocalMap的set方法。

由于关注的是ThreadLocal的对数据的存储和获取&#xff0c;所以这里我们只看 ThreadLocalMap的set和get方法&#xff1a;

  • ThreadLocalMap的set(ThreadLocal key, Object value)方法实现

private void set(ThreadLocal key, Object value) {Entry[] tab &#61; table;int len &#61; tab.length;//通过ThreadLocal去获取到在数组中的索引int i &#61; key.threadLocalHashCode & (len-1);//遍历Entry数组for (Entry e &#61; tab[i];e !&#61; null;e &#61; tab[i &#61; nextIndex(i, len)]) {ThreadLocal k &#61; e.get();//如果遍历到的Entry对象中的引用ThreadLocal与传进来的key相同时&#xff0c;//则将传进来的value赋值给Entry中的valueif (k &#61;&#61; key) {e.value &#61; value;return;}//如果遍历到的Entry对象中的引用ThreadLocal为null时&#xff0c;//则调用replaceStaleEntry()方法&#xff0c;此方法实现的大概思路在下边说明if (k &#61;&#61; null) {replaceStaleEntry(key, value, i);return;}}//如果以上两种情况都没有发生&#xff0c;则ThreadLocal和value封装成Entry并添加进Entry数组中tab[i] &#61; new Entry(key, value);int sz &#61; &#43;&#43;size;//长度加1//判断是否需要扩容if (!cleanSomeSlots(i, sz) && sz >&#61; threshold)rehash();}

  • replaceStaleEntry方法&#xff0c;里边大概逻辑是&#xff1a;
    接着i索引往下遍历找到一个存储的key与传进来的key相同的Entry对象&#xff0c;然后替换传进来的key和value&#xff0c;否则把传进来的ThreadLocal和value封装成Entry并添加进Entry数组中&#xff0c;最后决定要清除哪一些旧的Entry。

小总结&#xff1a;

  1. ThreadLocal的set方法里边的逻辑就是根据所在线程获取一个ThreadLocalMap对象&#xff0c;
  2. 如果对象不为null&#xff0c;则调用ThreadLocalMap的set方法&#xff0c;
  3. 否则创建一个ThreadLocalMap对象并赋值给线程的threadLocals属性。
  4. 在ThreadLocalMap的set方法中&#xff0c;遍历Entry数组&#xff0c;如果遍历到的Entry对象中的ThreadLocal对象与set方法传进来的key相同时&#xff0c;则直接将传进来的value替换Entry中的value值
  5. 如果Entry对象中的ThreadLocal对象为null&#xff0c;则继续从i向后遍历Entry数组&#xff0c;如果set方法传进来的key存在于后续的Entry对象&#xff08;后面用A对象表示&#xff09;中&#xff0c;则将A对象的value换为set方法传进来的value&#xff0c;然后将Entry[i]与A对象互换。

到这里可以知道值其实不是存储在ThreadLocal中&#xff0c;而是存储在ThreadLocalMap的Entry数组中的某个Entry中。实际上ThreadLocal只是作为Entry中的key。

  • 接下来是ThreadLocal的get方法

public T get() {Thread t &#61; Thread.currentThread();ThreadLocalMap map &#61; getMap(t);//根据所在线程获取ThreadLocalMap 对象if (map !&#61; null) {//根据ThreadLocal获取对应的Entry对象ThreadLocalMap.Entry e &#61; map.getEntry(this);if (e !&#61; null) {&#64;SuppressWarnings("unchecked")//获取Entry对象中的ValueT result &#61; (T)e.value;return result;}}//setInitialValue方法里边的逻辑跟set方法差不多&#xff0c;//只是将Value初始化为null在存储进去&#xff1b;然后返回一个nullreturn setInitialValue();}

  • ThreadLocalMap 中的getEntry方法的实现

private Entry getEntry(ThreadLocal key) {//根据当前的ThreadLocal对象来获取对应的索引int i &#61; key.threadLocalHashCode & (table.length - 1);Entry e &#61; table[i];if (e !&#61; null && e.get() &#61;&#61; key)return e;else//当ThreadLocal对应的Entry对象不存在时调用此方法//此方法的逻辑在下边说明return getEntryAfterMiss(key, i, e);}

  • getEntryAfterMiss(key, i, e)的逻辑&#xff1a;继续向下寻找一个与传进来的ThreadLocal对象相同的key所在的Entry&#xff0c;同时对key为null的Entry进行清除。

小总结

  1. ThreadLocal的get方法中&#xff0c;通过所在线程去获取ThreadLocalMap对象
  2. 通过ThreadLocalMap对象获取Entry对象
  3. ThreadLocalMap在获取Entry对象的过程中&#xff0c;同时对存放ThreadLocal为null的Entry进行清除

最后是ThreadLocalMap中的结构&#xff1a;

ThreadLocalMap是ThreadLocal的一个静态内部类

static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal> {Object value;//真正把值存放在此Entry(ThreadLocal k, Object v) {super(k);value &#61; v;}}//默认Entry数组容量private static final int INITIAL_CAPACITY &#61; 16;//Entry数组private Entry[] table;//当前元素长度private int size &#61; 0;//闸值&#xff0c;当当前元素长度大于等于此值时&#xff0c;会进行扩容//它等于 容量*2/3private int threshold;
}

总结


  • 不同线程中存放着不同的ThreadLocalMap对象
  • ThreadLocal并没有存放值&#xff0c;它只是充当key的角色&#xff0c;根据它的hash值可计算出在Entry数组中的位置&#xff0c;实际上值是存放在它的一个内部类ThreadLocalMap中的Entry
  • 在每次的get和set方法中&#xff0c;都会对key为null的Entry进行清除

推荐阅读
  • 本文介绍了使用C++Builder实现获取USB优盘序列号的方法,包括相关的代码和说明。通过该方法,可以获取指定盘符的USB优盘序列号,并将其存放在缓冲中。该方法可以在Windows系统中有效地获取USB优盘序列号,并且适用于C++Builder开发环境。 ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • IjustinheritedsomewebpageswhichusesMooTools.IneverusedMooTools.NowIneedtoaddsomef ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • 本文介绍了在iOS开发中使用UITextField实现字符限制的方法,包括利用代理方法和使用BNTextField-Limit库的实现策略。通过这些方法,开发者可以方便地限制UITextField的字符个数和输入规则。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 总结一下C中string的操作,来自〈CPrimer〉第四版。1.string对象的定义和初始化:strings1;空串strings2(s1);将s2初始 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 自动轮播,反转播放的ViewPagerAdapter的使用方法和效果展示
    本文介绍了如何使用自动轮播、反转播放的ViewPagerAdapter,并展示了其效果。该ViewPagerAdapter支持无限循环、触摸暂停、切换缩放等功能。同时提供了使用GIF.gif的示例和github地址。通过LoopFragmentPagerAdapter类的getActualCount、getActualItem和getActualPagerTitle方法可以实现自定义的循环效果和标题展示。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
author-avatar
秋夜里的寂寞
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有