ThreadLocal使用和原理简析
1. 解决共享资源冲突
对于并发工作,需要某种方式来防止两个任务同时访问相同的资源,至少在关键阶段不能出现这种冲突情况。
方法之一就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它,以此类推。Java中的synchronized、ReentrantLock就属于这种方式,关于这部分,前面有专门撰文详述:
第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储。因此,如果你有5个线程都要使用变量x所表示的对象,那线程本地存储就会生成5个用于x的不同的存储块。它使得你可以将状态与线程关联起来。创建和管理线程本地存储可以由java.lang.ThreadLocal类来实现。本文我们就来学习ThreadLocal的用法,并且从源码层面探究一下其实现原理。
按照官方的说法:
- ThreadLocal可以让线程拥有自己独享的变量,就是说多个线程共享同一个ThreadLocal对象,但是每个线程都可以通过ThreadLocal的get方法获取或set方法设置属于该线程的变量副本,变量只属于该线程并且其他线程无关;
- ThreadLocal对象一般作为类的private static属性,可以用来为线程设置一些状态信息比如userId或者transactionId;
2. ThreadLocal使用
到这里,如果之前没有使用过ThreadLocal,可能对于ThreadLocal的作用依然不清楚,我们通过官方的一个示例熟悉一下吧(如果之前使用过ThreadLocal的可以直接跳过此处):
public class ThreadLocalId {
private final static AtomicInteger nextId = new AtomicInteger();
private final static ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
// 重写ThreadLocal中的方法,用于如果没有通过set设置值时,第1次通过get来获取值时会调用这个方法生成初始值,可以重写该方法来指定初始值生成规则
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static Integer get() {
return threadId.get();
}
public static void main(String[] args) {
for(int i = ; i < ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
int id = ThreadLocalId.get();
System.out.println("thread:" + Thread.currentThread().getName() + ",threadId-->" + id);
threadId.set(id);
try {
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread:" + Thread.currentThread().getName() + ",threadId-->" + threadId.get());
}
}).start();
}
}
}
// 输出结果
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
thread:Thread-,threadId-->
如上,开启5个线程,每个线程都调用同一个ThreadLocal对象threadId的get方法获取一个id值作为线程标识,并通过set方法保存到ThreadLocal中,然后再通过get方法来获取,从输出结果上我们可以看,每个线程虽然都是操作的同一个ThreadLocal对象,但是它们获取到的值并没有被其它线程覆盖,都是自己set进去的值。这就是ThreadLocal的作用:提供线程本地变量,这样一来就不会受到其他线程的影响了,从而可以保证线程安全。那ThreadLocal又是如何做到的呢?我们来看一下源码:
3. 线程本地存储
我们先从其get()方法入手:
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();
}
ThreadLocalMap getMap(Thread t) {
// ThreadLocalMap作为Thread的一个属性
return t.threadLocals;
}
get方法的主要逻辑如下:
- 先获取当前线程t;
- 然后获取ThreadLocalMap;
- 如果map不为空,则以当前对象(ThreadLocal对象)为key获取value;
- 如果map为空,则执行初始化操作;
在第2步中,从哪里获取map?我们可以看到是从当前线程直接获取的(ThreadLocal作为Thread类的一个属性),也就是说这个map是属于当前线程的,而我们想要保存的值是存在这个map中的,这就是ThreadLocal的魔法所在,通过线程本地保存的方式来实现线程之间状态的互不干扰。
好,接下来我们看看如果是第一次调用get时ThreadLocalMap如果还没有的话是如何初始化ThreadLocalMap并生成初始值的:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
// 通过这个方法生成初始值,可以重写该方法来指定生成初始值规则
protected T initialValue() {
return null;
}
// new一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
setInitialValue的主要逻辑如下:
- 首先通过initialValue方法生成初始值;
- 然后获取ThreadLocalMap;
- 如果map不为空,则将第1步生成的值set进去,以当前对象(ThreadLocal对象)为key;
- 如果map为空,则new一个ThreadLocalMap出来;
- 返回生成的初始值;
现在我们知道了ThreadLocal的get方法是从一个保存在线程本地(就是Thread.currentThread()获取到的Thread实例对象中)的一个叫threadLocals的ThreadLocalMap的数据结构中,这是一个定制化的hash类型的map,专门用于保存线程本地变量。现在我们再看一下ThreadLocal是如何保存元素的:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
逻辑比较简单:
- 获取当前线程;
- 从当前线程获取ThreadLocalMap;
- 如果map不为空,则以当前ThreadLocal对象为key保存到ThreadLocalMap中;
- 如果map为空,则初始化ThreadLocalMap;
好了,现在我们知道ThreadLocal是通过将变量保存在线程本地来实现线程之间相互隔离的,可以结合下图一起理解。

4. 总结
- Java中通过java.lang.ThreadLocal类来实现创建和管理线程本地存储;
- 线程本地存储是指Thread类中的threadLocals,类型为ThreadLocalMap,其类定义在ThreadLocal中;
- 每个线程通过同一个ThreadLocal进行set和get变量时,实际上是以这个ThreadLocal为key进行存储和获取数据,线程之间共享的是ThreadLocal这个对象,但是数据是保存在线程各自本地;
ThreadLocalMap是保存数据的关键,它的内部原理又是怎样的呢?ThreadLocal又存在什么问题呢?请看下文。。。
ThreadLocal使用和原理简析的更多相关文章
- Java Android 注解(Annotation) 及几个常用开源项目注解原理简析
不少开源库(ButterKnife.Retrofit.ActiveAndroid等等)都用到了注解的方式来简化代码提高开发效率. 本文简单介绍下 Annotation 示例.概念及作用.分类.自定义. ...
- PHP的错误报错级别设置原理简析
原理简析 摘录php.ini文件的默认配置(php5.4): ; Common Values: ; E_ALL (Show all errors, warnings and notices inclu ...
- Java Annotation 及几个常用开源项目注解原理简析
PDF 版: Java Annotation.pdf, PPT 版:Java Annotation.pptx, Keynote 版:Java Annotation.key 一.Annotation 示 ...
- [转载] Thrift原理简析(JAVA)
转载自http://shift-alt-ctrl.iteye.com/blog/1987416 Apache Thrift是一个跨语言的服务框架,本质上为RPC,同时具有序列化.发序列化机制:当我们开 ...
- Spring系列.@EnableRedisHttpSession原理简析
在集群系统中,经常会需要将Session进行共享.不然会出现这样一个问题:用户在系统A上登陆以后,假如后续的一些操作被负载均衡到系统B上面,系统B发现本机上没有这个用户的Session,会强制让用户重 ...
- SIFT特征原理简析(HELU版)
SIFT(Scale-Invariant Feature Transform)是一种具有尺度不变性和光照不变性的特征描述子,也同时是一套特征提取的理论,首次由D. G. Lowe于2004年以< ...
- 基于IdentityServer4的OIDC实现单点登录(SSO)原理简析
写着前面 IdentityServer4的学习断断续续,兜兜转转,走了不少弯路,也花了不少时间.可能是因为没有阅读源码,也没有特别系统的学习资料,相关文章很多园子里的大佬都有涉及,有系列文章,比如: ...
- ARP攻击原理简析及防御措施
0x1 简介 网络欺骗攻击作为一种非常专业化的攻击手段,给网络安全管理者,带来严峻的考验.网络安全的战场已经从互联网蔓延到用户内部的网络, 特别是局域网.目前利用ARP欺骗的木马病毒在局域网中广泛传 ...
- Spring系列.AOP原理简析
Spring AOP使用简介 Spring的两大核心功能是IOC和AOP.当我们使用Spring的AOP功能时是很方便的.只需要进行下面的配置即可. @Component @Aspect public ...
随机推荐
- hive中left/right join on连接中and与where的使用问题
很多同学在进行表关联的时候,and和where的使用有时候分不清,在这里实操记录下. 创建人员信息表并写入数据 create table tmp.userinfo(id string,name str ...
- Java下载文件的几种方式
转发自博客园Sunny的文章 1.以流的方式下载 public HttpServletResponse download(String path, HttpServletResponse respon ...
- 利用jquery-barcode.js实现生成条形码
jquery-barcode官网 js下载地址-github 代码示范(官网上也有) <!DOCTYPE html> <html> <head> <meta ...
- Bootstarp 使用布局
实现 Bootstrap 基本布局 看到了一篇 20 分钟打造 Bootstrap 站点的文章,内容有点老,重新使用 Bootstrap3 实现一下,将涉及的内容也尽可能详细说明. 1. 创建基本的页 ...
- windows服务定时任务
其实定时任务时不时会碰到,只不过解决方案也不是只有一个,网上也有很多文章,但是没有一篇说得很清楚,尤其是安装环节,今天就着重说一下安装, 其他步骤带过,C#开发windows服务,开发,安装,调试 1 ...
- 你不知道的JS之作用域和闭包(四)(声明)提升
原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...
- ubuntu tensorflow install(Ubuntu16.04+CUDA9.0+cuDNN7.5+Python3.6+TensorFlow1.5)
在网上找了很多案例,踩了许多坑,感觉比较全面的是下面介绍的 http://www.cnblogs.com/xuliangxing/p/7575586.html 先说说我的步骤: 首先安装了Anacod ...
- Azure Web连接到Azure MySql Db
这个问题折腾了好一会,简单记录一下. 两种方式: 输入"规则名称"."起始 IP"和"结束 IP",然后单击"保存". ...
- .NET Core 微服务架构 Steeltoe 使用(基于 Spring Cloud)
阅读目录: 1. Spring Cloud Eureka 注册服务及调用 2. Spring Cloud Hystrix 断路器 3. Spring Cloud Hystrix 指标监控 4. Spr ...
- 解决 spring-cloud-starter-zipkin 启动错误
应用场景:Spring Boot 服务添加 Zipkin 依赖,进行服务调用的数据采集,然后进行 Zipkin-Server 服务调用追踪显示. 示例pom.xml配置: <parent> ...