九浅一深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.深感前端变化之大,各种工具.框架令人眼花缭乱.不过正是这些变化,让前端开发更灵活. 博主在刚开始时,参考官网的各个步骤以及 ...
随机推荐
- redis知识积累
redis 安装包:redis-3.0.6.tar.gz 打开 redis: cd /media/deepl/文档/ubuntutools/redis-3.0.6/src/ ./redis-serve ...
- Linux下启动tomcat报错RROR org.apache.catalina.core.StandardContext- Error starting static Resources java.lang.IllegalArgumentException: Document base /home/duiba/apache-tomcat/webapps/../webapps/manager do
部署项目的时候,重启tomcat,死活起不来,很郁闷,网上巴拉了半天,结合自己的情况,找到了原因: 错误日志信息: 2018-12-13 13:52:26,992 [main] INFO org.ap ...
- 信息安全技术PGP实验 20155205 20155218
信息安全技术PGP实验 一.实验过程 PGP的安装与设置(Mac版) 访问GPGTools网站,下载GPG Suite for OS X,下载完毕,双击dmg文件,运行安装操作. 创建PGP密钥(一定 ...
- MapServer和GeoServer对比
https://blog.csdn.net/theonegis/article/details/45823099
- spring-数据库操作组件的实现
Ok,今天看了下spring数据库组件实现的源码,简要的做下笔记. 首先是JdbcTemplate,其实这个技术的实现很简单,就是一个模板类,把执行sql的主干流程都写好了,比如connection的 ...
- less(css)语言快速入门
转载 原文地址:https://www.ibm.com/developerworks/cn/web/1207_zhaoch_lesscss/ 简介 CSS(层叠样式表)是一门历史悠久的标记性语言,同 ...
- iOS笔记之UIKit_UIButton
//UIButton的基本属性 _btn = [UIButton buttonWithType:UIButtonTypeCustom]; _btn.frame = CGRectMake(0, 200, ...
- DXP 板层
一)DXP-设置板层(D+K )在PCB编辑 Design->Layer Stack Manager(层管理) 1)快捷命令 D + K 进入么多层置管理器 2.鼠标右键 TopLayer- ...
- 关于Java-枚举的总结
枚举 枚举的定义 枚举也是JDK5.0的新特性. JDK5.0加入了一个全新类型的“类”——枚举类型. 为此引入了一个新的关键字enum. 可以这样来定义一个枚举类型: public enum Col ...
- ORACLE EBS常用表
http://www.cnblogs.com/quanweiru/archive/2012/09/26/2704628.html call fnd_global.APPS_INITIALIZE(131 ...