文章目录

  • ThreadLocal概述
  • 使用案例
    • 领域实体
    • ThreadLocal操作类
    • 使用ThreadLocal变量
    • 输出
  • 原理解析
    • 常用方法
    • ThreadLocal的set()方法
    • get方法
    • remove方法
    • 理解ThreadLocalMap对象
  • 常见的坑点

ThreadLocal概述

ThreadLocal是通过线程隔离的方式防止任务在共享资源上产生冲突, 线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。

ThreadLocal是一个将在多线程中为每一个线程创建单独的变量副本的类; 当使用ThreadLocal来维护变量时, ThreadLocal会为每个线程创建单独的变量副本, 避免因多线程操作共享变量而导致的数据不一致的情况。

注意:
但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal要大。

使用案例

笔者以汽车领域为案例,请求的处理会话线程,持有当前车机系统的信息:系统名称、版本,作为ThreadLocal的变量内容,实际的应用中通常将操作该变量的操作单独封装成一个类。

领域实体

package com.linfanchen.springboot.lab.tr;class OsBO {/*** 车机系统名称*/private String osName;/*** 车机系统版本*/private Integer version;public String getOsName() {return osName;}public void setOsName(String osName) {this.osName = osName;}public Integer getVersion() {return version;}public void setVersion(Integer version) {this.version = version;}
}

ThreadLocal操作类

package com.linfanchen.springboot.lab.tr;import java.util.HashMap;public class LocalOS {/*** 线程持有的变量*/public static final ThreadLocal<HashMap<String, OsBO>> localOS = new ThreadLocal<>();/*** 展示车机系统*/static void showOs(String carName) {// 输出本地变量System.out.println(carName + " 的车机系统名称:" + localOS.get().get("os").getOsName() + ", 版本:" + localOS.get().get("os").getVersion());// 清除本地变量的值,防止内存泄漏localOS.remove();}}

使用ThreadLocal变量

package com.linfanchen.springboot.lab.tr;import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.util.HashMap;@RunWith(SpringRunner.class)
@SpringBootTest
public class ThreadLocalTest {@Testpublic void firstTest() throws InterruptedException {/*** 创建线程,模拟实际场景中处理请求会话的线程*/new Thread(new Runnable() {@Overridepublic void run() {HashMap<String, OsBO> localOsValue = new HashMap<>();OsBO osBO = new OsBO();osBO.setOsName("IDrive");osBO.setVersion(7);localOsValue.put("os", osBO);LocalOS.localOS.set(localOsValue);// 获取当前线程的操作系统LocalOS.showOs("BMW");// 删除后再打印System.out.println("BMW 线程下删除后再打印:" + LocalOS.localOS.get());}}, "BMW").start();/*** 创建线程,模拟实际场景中处理请求会话的线程*/new Thread(new Runnable() {@Overridepublic void run() {HashMap<String, OsBO> localOsValue = new HashMap<>();OsBO osBO = new OsBO();osBO.setOsName("MBUS");osBO.setVersion(1);localOsValue.put("os", osBO);LocalOS.localOS.set(localOsValue);// 获取当前线程的操作系统LocalOS.showOs("Benz");// 删除后再打印System.out.println("Benz 线程下删除后再打印:" + LocalOS.localOS.get());}}, "Benz").start();}
}

输出

BMW 的车机系统名称:IDrive, 版本:7
Benz 的车机系统名称:MBUS, 版本:1
BMW 线程下删除后再打印:null
Benz 线程下删除后再打印:null

原理解析

常用方法

方法 作用
ThreadLocal() 创建ThreadLocal对象
public void set( T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public T remove() 移除当前线程绑定的局部变量,该方法可以帮助JVM进行GC
protected T initialValue() 返回该线程局部变量的初始值

  • 每个Thread维护一个ThreadLocalMap,这个ThreadLocalMap的key是ThreadLocal实例本身,value才是真正要存储的值Object。
  • 每个Thread线程内部都有一个ThreadLocalMap。
  • Map里面存储ThreadLocal对象(key)和线程的变量副本(value)。
  • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
  • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

这样设计的好处:

  • 减少ThreadLocalMap存储的Entry数量:因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。在实际运用当中,往往ThreadLocal的数量要少于Thread的数量。
  • 当Thread销毁之后,对应的ThreadLocalMap也会随之销毁,能减少内存的使用(但是不能避免内存泄漏问题,解决内存泄漏问题应该在使用完后及时调用remove()对ThreadMap里的Entry对象进行移除,由于Entry继承了弱引用类,会在下次GC时被JVM回收)。

ThreadLocal的set()方法

设置当前线程绑定的局部变量


public void set(T value) {// 1.获取当前线程对象Thread t = Thread.currentThread();// 2.获取当前线程的threadLocalsThreadLocal.ThreadLocalMap map = this.getMap(t);if (map != null) { // 3.1如果 threadLocals 非空,添加 threadLocals 值,key为当前 ThreadLocal实例对象,value为传入的待设值map.set(this, value);} else { // 3.2如果 threadLocals 为空, 则进行创建初始化操作this.createMap(t, value);}
}/**
* 根据当前线程获取线程内部的Thread.threadLocals属性(类型为: ThreadLocalMap)
*
* @param  t the current thread 当前线程
* @return the map 对应维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}/**
* 创建初始化,key为当前 ThreadLocal 实例对象
*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}

get方法

/**
* 获取当前线程中保存ThreadLocal的值
*
*   如果当前线程没有此ThreadLocal变量,则它会通过调用initialValue方法进行初始化值
*
*/
public T get() {// 获取当前线程对象Thread t = Thread.currentThread();// 获取此线程对象中维护的ThreadLocalMap对象ThreadLocal.ThreadLocalMap map = this.getMap(t);if (map != null) {// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体eThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {// 获取存储实体 e 对应的 value值,即为我们想要的当前线程对应此ThreadLocal的值T result = e.value;return result;}}return this.setInitialValue();}/**
* 初始化值
*/
private T setInitialValue() {T value = this.initialValue();Thread t = Thread.currentThread();// 根据当前线程获取mapThreadLocal.ThreadLocalMap map = this.getMap(t);if (map != null) {// 获取到了map,则设置对应的值map.set(this, value);} else {// 没有获取到则创建mapthis.createMap(t, value);}// 判断对象的类是否 TerminatingThreadLocal,如果是则注册上去if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal)this);}return value;}/**
* 默认的初始值为null
* 如果想ThreadLocal线程局部变量有一个除null以外的初始值,
* 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
* 通常, 可以通过匿名内部类的方式实现
*/
protected T initialValue() {return null;}

执行流程:

remove方法

/**
* 删除当前线程中保存的ThreadLocal对应的实体entry
*
*/
public void remove() {// 1.获取当前线程对象中维护的ThreadLocalMap对象ThreadLocal.ThreadLocalMap m = this.getMap(Thread.currentThread());if (m != null) {// 2.存在则调用map.remove// 以当前ThreadLocal为key删除对应的实体entrym.remove(this);}}/**
* m.remove(this)为此方法
*/
private void remove(ThreadLocal<?> key) {ThreadLocal.ThreadLocalMap.Entry[] tab = this.table;int len = tab.length;int i = key.threadLocalHashCode & len - 1;// 遍历ThreadLocalMapfor(ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {// 比较每一个entry,找到相等的进行清理if (e.get() == key) {e.clear();this.expungeStaleEntry(i);return;}}}

理解ThreadLocalMap对象

本质上来讲, 它就是一个Map, 但是这个ThreadLocalMap与我们平时见到的Map有点不一样:

  • 它没有实现Map接口;
  • 它没有public的方法, 最多有一个default的构造方法, 因为这个ThreadLocalMap的方法仅仅在ThreadLocal类中调用, 属于静态内部类;
  • ThreadLocalMap的Entry实现继承了WeakReference<ThreadLocal<?>> ;
  • 该方法仅仅用了一个Entry数组来存储Key, Value; Entry并不是链表形式, 而是每个bucket里面仅仅放一个Entry;

常见的坑点

  • 内存泄漏
  • hash冲突

参考文档:
https://blog.csdn.net/u010445301/article/details/111322569

https://blog.csdn.net/silence_yb/article/details/124265702

https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html

https://zhuanlan.zhihu.com/p/34406557

https://www.cnblogs.com/eric-fang/p/13680015.html

https://blog.csdn.net/fascinate_/article/details/113524837

https://zhuanlan.zhihu.com/p/346291694

ThreadLocal详解

掌握ThreadLocal的王者段位相关推荐

  1. LOL:各服务器王者段位人数改动,越南菲律宾与韩国并列共300人

    我们没有在明确公布过各地区王者段位人数的改动.基本上我们是把规模相似的赛区归到一起并相应地分配王者段位的人数来优化竞争.王者段位人数分为50,200和300剧情.以下是具体分配: 王者段位人数为50的 ...

  2. 王者荣耀服务器什么时候增加人数,王者荣耀:大区最强王者段位人数超过1万,白菜大神遍地是!...

    这里是游戏震惊部,王者荣耀马上要进入到S14赛季了,还剩下不到5天的时间,很多人会在这个时间段冲击自己理想中的段位,本部也是冲上最强王者,按照自己设定的"辅助+射手"上段,不过感觉 ...

  3. 我的世界服务器如何显示svip,SVIP如何设置王者段位显示 | 手游网游页游攻略大全...

    发布时间:2016-05-12 球球大作战云朵名字怎么设置,云朵名字显示攻略.下面小编就给大家带来这篇攻略,大家一起来看看吧. 球球大作战云朵名字怎么设置 云朵名字显示攻略 在游戏中,我们进入修改昵称 ...

  4. 学会这些实用又牛逼的Word技巧,你离Word的王者段位就不远了

    我们大家都知道Word其实相比起其他办公软件要简单一些,但是要怎样才可以让自己的Word变得更加厉害,成为王者呢?今天小编将来分享几个Word中那些实用技巧,助大家一臂之力! 一.快速制作分割线 各种 ...

  5. 在JSRUN写一段王者荣耀段位代码

    在线运行:http://js.jsrun.net/vJfKp/edit 上图:, // var xs=130; var xs=131;if(xs>=0 && xs<=9){ ...

  6. 王者服务器维护段位掉了,王者荣耀s13赛季段位掉段规则 s13段位重置掉段继承表[图]...

    王者荣耀s13赛季段位会掉多少,新赛季开始的时候会重置到什么阶段呢?或许有的玩家朋友很好奇吧,下面是友情MT为大家带来的王者荣耀s13赛季段位掉段规则,s13段位重置掉段继承表,希望能帮助到大家! 王 ...

  7. 打印5列五颗星_王者荣耀段位排列每个段位多少星?段位排列标志多久刷新一次?[多图]...

    王者荣耀段位排列有很多个不同的段位,那么这些段位中每个段位是多少星?玩家们比较在乎段位的问题,那么今天游戏鸟小编就带着大家一起来了解段位排列的情况,另外下面还会有段位排列标志的刷新时间介绍哦! 王者荣 ...

  8. 打印5列五颗星_王者荣耀段位排列每个段位多少星?段位排列标志多久刷新一次?...

    王者荣耀段位排列有很多个不同的段位,那么这些段位中每个段位是多少星?玩家们比较在乎段位的问题,那么今天游戏鸟小编就带着大家一起来了解段位排列的情况,另外下面还会有段位排列标志的刷新时间介绍哦! 王者荣 ...

  9. 王者荣耀s20赛季服务器维护,王者荣耀s20赛季开启时间 王者荣耀s20赛季段位继承规则及赛季皮肤...

    最近玩王者荣耀的玩家都在关注王者荣耀S20赛季什么时候开始,因为S19的战令已经结束了,相信不久就是S20赛季了,下面就是S20赛季的开始时间以及新版本内容介绍了,感兴趣的玩家就随小编来了解一下吧! ...

最新文章

  1. PHP框架编写和应用知识点,写PHP框架需要具备那些知识?
  2. npoi 所有列调整为一页_必能用到,一页PPT中想放超多图片怎么办?
  3. sscanf取固定长度的int_sscanf函数用法详解-阿里云开发者社区
  4. Hadoop RPC框架
  5. C语言课后习题(11)
  6. 利用zabbix监控mysqldump定时备份数据库是否成功 乐维君
  7. WPS配置工具参数 ksomisc.exe
  8. CMake语法及CMakeList.txt简单使用
  9. vim 全局替换_VIM学习笔记 Ex模式 (Ex Mode)
  10. 【原创】关于数字万用表的知识-2021-02-15
  11. 腾讯云安装宝塔面板详细教程
  12. 小米手机助手linux,小米手机助手怎么用?小米手机助手教程
  13. Echarts 配置渐变
  14. 中文地址翻译成英文实例
  15. 陶哲轩实分析 3.2 节 习题试解
  16. Sudoku Killer(搜索)
  17. html5首字母大小写,css中如何设置英文首字母大写
  18. 自然语言处理(NLP)技术在医疗保健领域中的八个案例
  19. 《CSS世界》读书笔记:line-height
  20. 浏览器源代码下载图片原图,手把手教学

热门文章

  1. myyearbook分析
  2. 天命之子服务器不稳定,天命之子国际服反和谐
  3. 机器学习逐渐放弃:分类(一)
  4. 游戏中常说的“周目”是什么意思呢?
  5. 安卓手机开不了机_苹果6黑屏开不了机怎么办
  6. 推荐系统遇上深度学习(二十)--探秘阿里之完整空间多任务模型ESSM
  7. 语音聊天室源码开发点赞功能,用 MySQL 还是 Redis ?
  8. Win7支持的Node.js最新版本
  9. 专业版win10多用户同时远程登录方法
  10. 使用hover时,鼠标放在上面移动时会不停的触发和闪动