九浅一深ThreadLocal
ThreadLocal的作用、使用示例
ThreadLocal是线程的本地存储,存储在其内的值只能被当前线程访问到,其他线程获取不到,可以存储任意对象。
经常用来存储当前线程的一些上下文信息,这样不用通过参数一层层的向下传递。比如在计算分库分表时,上层根据业务规则计算出这次要操作的数据库和表编号,存储到ThreadLocal中,下层可以通过ThreadLocal直接获取到。
如果当前线程创建了子线程,某些数据想让子线程也获取到,可以使用InheritableThreadLocal,用法和ThreadLocal类似。
具体使用代码如下:
public class Demo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private static InheritableThreadLocal inheritableThreadLocal = new InheritableThreadLocal(); public static void main(String[] args) {
threadLocal.set("threadlocal value");
System.out.println(Thread.currentThread().getName() + " thread get threadlocal value : " + threadLocal.get());
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " thread get threadlocal value : " + threadLocal.get());
},"child").start(); inheritableThreadLocal.set("inheritable threadlocal value");
System.out.println(Thread.currentThread().getName() + " thread get inheritable threadlocal value:" + inheritableThreadLocal.get());
new Thread(()->{
System.out.println(Thread.currentThread().getName() + " thread get inheritable threadlocal value : " + inheritableThreadLocal.get());
},"child").start();
}
}
执行结果:
main thread get threadlocal value : threadlocal value
main thread get inheritable threadlocal value:inheritable threadlocal value
child thread get threadlocal value : null
child thread get inheritable threadlocal value : inheritable threadlocal value
可以看到存储到ThreadLocal中的对象只能被当前线程访问到,存储到InheritableThreadLocal中的对象可以被当前线程以及子线程访问到。
实现原理及源码分析
先不看TheadLocal的源码,思考下如果让我们实现类似功能该怎么做。
首先,数据只能被当前线程访问到,最合适存储的地方肯定是Thread的一个字段,每个线程都是Thread的一个实例,都可以通过Thread.currentThread()获取到,进而可以获取到当前线程的本地存储。其他线程通过Thread.currentThread()获取到的是各自的Thread实例,所以不同线程存储的数据做到了相互隔离。
然后看下应该用什么样的数据结构。要存储数据有可能不只一条,而且有存有取是一个map结构,于是有了一个初步实现,伪代码如下:
Thread{
Map threadLocalMap = nulll;
}
ThreadLocal{
public set(val){
if(Thread.currentThread().threadLocalMap == null){
Thread.currentThread().threadLocalMap = new HashMap();
}
Thread.currentThread().threadLocalMap.put(this,val);
}
public Object get(){
return Thread.currentThread().threadLocalMap.get(this);
}
}
使用this中作为key,这样一个threadLocal代表一条数据,多个线程可能持有同一个this,但通过this拿到的值是存储在各种threadLocalMap中的值。
下面看下ThreadLocal的源码,发现思路类似。Thread中有一个map结构的ThreadLocalMap,调用ThreadLocal.set(val)方法时,实际是以this为key,val为value存储到该map中。
具体实现上有很多不同之处,Thread.threadLocalMap访问权限是当前包,也就是对于使用方只能通过ThreadLocal暴露的方法进行操作,可以防止其他线程拿到Thread对象后对threadLcoalMap访问,引起安全性问题。
然后注意到java没有使用HashMap而是自己实现了一个ThreadLocalMap,这是出于什么原因呢?
根据ThreadLocalMap的代码,它依然是一个Hash表,但是在解决hash冲突时,不同于HashMap采用的链地址法,而是采用线行探查法。两种解决Hash冲突方法的示意图如下:
链地址法 线行探查法
链地址法:计算key的hash值并取模,存储到对应的坑位,如果发现该坑位已经被占用,则创建一个链表节点,挂在该坑位的最后,查找时,根据key的hash值计算找到对应坑位,然后遍历坑位下的链表,知道找到key相同的节点。
线行探查法:计算完key的哈希值并取模后,如果发现Hash表(数组)中对应的坑位已经存在了数据,则找该坑位的下一个坑,直到找到没有数据的坑位,进行存储。
除这两种,还有再哈希法、公共溢出区法等,在此不做详细论述。
链表法适用于经常进行插入和删除的情况,插入和删除时只需要把对应坑位的链表节点插入和删除即可,不会影响到其他坑位的数据。缺点是容易浪费空间,如果hash表有0~9十个链表,我们有十条数据但都hash冲突比较多,有可能只有第一个链表有十个数据,其他链表都是空的。而且,散列比较均匀,每个链表有一个数据,除了存储该数据,还需要存储头结点
线行探查法的有点是没有空间的浪费,只需要存储数据。缺点是不适合经常删除,如果删除一个结点,该结点之后的数据都需要重新计算要入到哪个坑位。或者可以假删除,等有下个元素的时候再进行替换。
ThreadLocal使用场景是不会频繁插入,基本不会有删除操作,所以更适合线行探查法。
使用线行探查法,要尽量保证hash值取模后能尽量均匀的三列在每个坑位上,提高插入和查找的效率。
ThreadLocalMap在计算hash值的时候采用了0x61c88647,可以保证散列的非常均匀,基本没有hash冲突。计算this,即ThreadLocal对象的hash值时,返回的是0x61c88647或0x61c88647的倍数。0x61c88647是根据斐波那契散列法得到的一个值,对应着32位有符号整数中的2654435769,用它对2的幂进行取模可以保证散列的非常均匀。
源码中ThreadLocalMap的每个Entry都是弱引用,这是为什么呢?
这个也是重新实现map,而没有使用HashMap的原因之一。ThreadLocalMap是存储在Thread对象里的,线程不结束,这个map就会永远存在不会被GC,如果一个线程之前存储了很多数据到ThrealLocal中,现在已经不用了,但没有删除,如果强引用,这些数据就不会被GC。设置成弱引用,当ThreadLocal对象已经不被引用时,不会因为被ThreadLocalMap而引用,导致无法回收内存。
可被子线程继承的本地存储 InheritableThreadLocal 实现原理解析
InheritableThreadLocal使用模板方法模式,继承自ThreadLocal。
和ThreadLocal相比,InheritableThreadLocal只有一点差别:数据存在在Thread对象的inheritableThreadLocal字段,而不是ThreadLocal字段。所以只覆盖了ThreadLcoal的getMap方法和createMap方法,分别用于获取map和给Thread.inheritableThreadLocal赋值。
在创建一个线程的时候,在线程的init方法中可以看到如下代码:
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
inheritThreadLocals是init方法的参数,默认为true。只要父线程的inheritableThreadLocals字段不为空,在创建子线程的时候就会把这些数据复制给子线程。
ThreadLocal.createInheritedMap方法是一个工厂方法,这个方法用于创建一个map对象,并把参数传递进来的map复制到新map中,需要注意的是这个复制是浅复制,map中存的是原对象的引用。示例代码如下
public class Demo {
private static InheritableThreadLocal<User> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
/**
* 更新对象属性示例
*/
User user = new User();
user.name = "lanlingwang";
inheritableThreadLocal.set(user);
Thread thread = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " thread get user.name : " + inheritableThreadLocal.get().name);
},"child1"); inheritableThreadLocal.get().name = "ake"; thread.start();
System.out.println(Thread.currentThread().getName() + " thread get user.name : " + inheritableThreadLocal.get().name); /**
* 更新对象示例
*/
Thread thread2 = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " thread get user.name : " + inheritableThreadLocal.get().name);
},"child2"); User user2 = new User();
user2.name = "nakelulu";
inheritableThreadLocal.set(user2); thread2.start();
System.out.println(Thread.currentThread().getName() + " thread get user.name : " + inheritableThreadLocal.get().name);
}
}
class User{
public String name ;
}
如果ThreadLocal中存储了一个User对象,其中一个线程拿到这个对象后更改了name字段,这个更改对其他线程是可见的。如果某个线程重新往ThreadLocal中set了一个新对象,只是set到当前线程而已,不会修改其他线程的数据,因为各个线程的ThreadLocalMap是独立的,只是在创建线程的时候浅复制了一份而已。
九浅一深ThreadLocal的更多相关文章
- 【Python】(六)Python数据类型-列表和元组,九浅一深,用得到
您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本文分十个章节介绍数据类型中的列表(list)和元组(tuple),从使用说到底层实现,包您满意 干货满满,建议收藏,需要用到时常看看. 小伙伴们 ...
- 浅入深出之Java集合框架(上)
Java中的集合框架(上) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- 浅入深出之Java集合框架(中)
Java中的集合框架(中) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,如果已经有java基础的小伙伴可以直接跳到<浅入深出之Java集合框架 ...
- 浅入深出之Java集合框架(下)
Java中的集合框架(下) 由于Java中的集合框架的内容比较多,在这里分为三个部分介绍Java的集合框架,内容是从浅到深,哈哈这篇其实也还是基础,惊不惊喜意不意外 ̄▽ ̄ 写文真的好累,懒得写了.. ...
- 浅入深出Vue:环境搭建
浅入深出Vue:环境搭建 工欲善其事必先利其器,该搭建我们的环境了. 安装NPM 所有工具的下载地址都可以在导航篇中找到,这里我们下载的是最新版本的NodeJS Windows安装程序 下载下来后,直 ...
- 浅入深出Vue:工具准备之PostMan安装配置及Mock服务配置
浅入深出Vue之工具准备(二):PostMan安装配置 由于家中有事,文章没顾得上.在此说声抱歉,这是工具准备的最后一章. 接下来就是开始环境搭建了~尽情期待 工欲善其事必先利其器,让我们先做好准备工 ...
- 浅入深出Vue:工具准备之WebStorm安装配置
浅入深出Vue之工具准备(一):WebStorm安装配置 工欲善其事必先利其器,让我们先做好准备工作吧 导航篇 WebStorm安装配置 所有工具的下载地址都可以在导航篇中找到,这里我们下载的是最新版 ...
- 浅入深出Vue系列
浅入深出Vue导航 导航帖,直接点击标题即可. 文中所有涉及到的资源链接均在最下方列举出来了. 前言 基础篇 浅入深出Vue:工具准备之WebStorm搭建及配置 浅入深出Vue之工具准备(二):Po ...
- 浅入深出Vue:前言
浅入深出Vue系列文章 之前大部分是在做后端,后来出于某些原因开始接触Vue.深感前端变化之大,各种工具.框架令人眼花缭乱.不过正是这些变化,让前端开发更灵活. 博主在刚开始时,参考官网的各个步骤以及 ...
随机推荐
- timerfd与eventfd
1.timerfd timerfd是定时器描述符,通过timerfd_create()来创建它,timerfd_settime()来设置定时器时间,当时间到期定时器文件描述符就可读,所以能够在sele ...
- JAVA技术路线2
https://www.zhihu.com/question/56110328 1.JavaSE学习视频 http://pan.baidu.com/s/1bp3g6rd2.javaweb的学习视频 h ...
- i2c_client的生成
网上很多文档都是介绍源码,包括i2c_client结构体的源码都有贴出,看上去似乎需要手动写该结构体,但实际上,i2c_client的生成是用如下方法. \arch\arm\mach-omap2/bo ...
- idea常用插件介绍
常用插件 mybatis mapper 选择plugins,搜索mybatis plugin 激活教程 使用 插件的使用
- js基础学习笔记(六)
事件(可以被 JavaScript 侦测到的行为) 主要事件表: 加载事件(onload) 事件会在页面加载完成后立即发生,同时执行被调用的程序. 卸载事件(onunload) 当用户退出页面时(页面 ...
- Alpha阶段敏捷冲刺(二)
1.提供当天站立式会议照片一张. 2.每个人的工作 (有work item 的ID),并将其记录在码云项目管理中: 昨天已完成的工作. 祁泽文:上网了解了艾宾浩斯遗忘曲线算法. 徐璐琳:找交互模块的源 ...
- CAAnimation-CAPropertyAnimation-CABasicAnimation-CAKeyframeAnimation
参考博客 iOS关于CoreAnimation动画知识总结 http://www.cnblogs.com/wujy/p/5203995.html iOSCoreAnimation动画系列教程(一):C ...
- Xcode常见快捷键
在项目工作中,你每天都要和这些视图互动,所有这些视图在Xode中都是必不可少的.所以接下来江哥将教你如何快速通过热键来配置你的工作空间. Command (⌘):用来导航,主要用来控制导航区域. Al ...
- 5.css背景以及书写位置
1.样式表书写位置 ◆内嵌式写法 <head> <style type=”text/css”> 样式表写法 </style> </head> 2.◆外链 ...
- java解决共享资源竞争
由于多线程的实现,在运行一个程序的时候可能会有很多的线程在同时运行,但是线程的调度并不是可见的,所以不会知道一个线程什么时候在运行,比如说 你坐在桌子前手拿着叉子,正要去叉盘中的最后一片食物,当你的叉 ...