ThreadLocal

线程数据共享和安全

1.什么是ThreadLocal?

  1. ThreadLocal的作用,可以实现在同一个线程数据共享,从而解决多线程数据安全问题

    当http请求发送到Tomcat服务端时,Tomcat会创建一个线程去处理这个http请求,如果是请求servlet,servlet可能又会调用其他service,在这些service中,又可能会调用dao,去对数据库进行操作。

    在这些资源或者方法的调用中,为解决数据安全问题,在这一个线程执行的过程中,我们希望有一个数据是共享的,而且是安全的。

    应用场景:比如说事务控制,一个线程可能涉及到多个service的调用,调用多个dao,在这过程中,可能对数据库的多张表进行了操作。这时我们希望在整个业务流程结束之后,再进行一次提交commit。反过来说,在没有进行提交之前,我们希望始终是一个connection在操作,这样才能在结束时进行统一的一次提交(在开始操作的时候将自动提交设置为false)。

    这样就可以解决同一个请求中,调用多个service或者多个dao的需求,这个需求也是开发中必须解决的事务安全问题(事务一致性需求)。

    ThreadLocal技术就能够很好地解决这个问题。我们可以在实际开发中使用Filter和ThreadLocal解决事务安全问题。

  2. 一个ThreadLocal对象可以给当前线程关联一个数据(普通变量,对象,对组)--使用set方法

  3. ThreadLocal可以像Map一样存取数据,key为当前的ThreadLocal对象--使用get方法

  4. 每一个ThreadLocal对象只能为当前线程关联一个数据,如果要为当前线程关联多个数据,就需要使用多个ThreadLocal对象实例

  5. 每个ThreadLocal对象实例定义的时候,一般为static类型

  6. ThreadLocal中保存的数据,在线程销毁之后,会自动释放

2.ThreadLocal快速入门

2.1ThreadLocal的类图

如下:ThreadLocal类中常用的方法有get(),set(),getMap()等,ThreadLocal类中含有一个重要的内部类ThreadLocalMap,ThreadLocalMap类中又含有一个内部类Entry,数据以key-value的形式存放在Entry中。

  • ThreadLocal核心的价值就是:在一个线程中,以线程安全的方式来共享数据。

2.2应用实例

需求: 演示 ThreadLocal (作用:在一个线程中, 共享数据(线程安全))的使用

Dog:

package com.li.threadlocal;

public class Dog {
}

T1:

package com.li.threadlocal;

public class T1 {
//创建ThreadLocal对象, public static修饰
public static ThreadLocal<Object> threadLocal1 = new ThreadLocal<>(); public static void main(String[] args) {
new Thread(new Task()).start();//启动一个新的线程,注意不是主线程
} //Task是一个线程类,同时是一个内部类
public static class Task implements Runnable {
@Override
public void run() {
Dog dog = new Dog();
Pig pig = new Pig();
//给threadLocal1对象放入dog
System.out.println("Task 放入了 dog=" + dog);
threadLocal1.set(dog);
System.out.println("Task 的 run 方法中的线程= " + Thread.currentThread().getName());
new T1Service().update();
}
}
}

T1Service:

package com.li.threadlocal;

public class T1Service {
public void update() {
//取出threadLocal1对象关联的对象
Object o = T1.threadLocal1.get();
//获取当前线程名
String name = Thread.currentThread().getName();
System.out.println("在T1Service 的update()的线程是= " + name + ", 取出dog= " + o);
//调用了dao-update()方法
new T1DAO().update();
}
}

T1DAO:

package com.li.threadlocal;

public class T1DAO {
public void update() {
//取出线程关联的threadLocal1对象的数据
Object o = T1.threadLocal1.get();
//获取当前线程的名称
String name = Thread.currentThread().getName();
System.out.println("在T1DAO 的update()的线程是= " + name + ", 取出dog= " + o);
}
}

可以看到所有方法中拿到的对象都是同一个:

3.源码分析

3.1ThreadLocal的set()

在2.2的应用实例中,我们在T1类中使用了ThreadLocal.set()方法,现在来看看set()方法的底层源码:

set()方法关联的其他方法和属性:



从上述代码中我们可以知道set方法的主要工作如下:

public void set(T value) {
//1.获取当前线程
Thread t = Thread.currentThread(); //2.通过线程对象,获取到和此线程关联的ThreadLocalMap
// (ThreadLocalMap是ThreadLocal里的一个静态内部类*,
// 类型是ThreadLocal$ThreadLocalMap)
ThreadLocalMap map = getMap(t); //3.如果获取到的ThreadLocalMap不为空,就将传入的数据放入map,其中:
// -key为当前的ThreadLocal对象(this) -value为存放的数据,
// 因为key值不能重复(map性质),一个ThreadLocal对象只能存放一个数据
// 如果再赋值,就会替换旧的value值
if (map != null)
map.set(this, value); //4.如果和当前线程关联的ThreadLocalMap为null,
//就创建一个和当前线程关联的ThreadLocalMap,并且将存放的数据作为value放入map,
//这里的key为当前线程t(作用是让线程和创建的map关联起来)
else
createMap(t, value);
}

在2.2应用实例的threadLocal1.set(dog);语句旁打上断点,点击debug,点击step over。

如下图所示,可以看到子线程Thread-0中有一个threadLocals属性(该属性的类型为ThreadLocalMap),该map中又有一个table属性(table的类型为Entry[]数组), table数组存放Entry对。

这里涉及到的弱引用暂不深入

在Entry对中,以k-v的形式存放数据,key值为 当前的线程中 的ThreadLocal对象,value值为存放的数据。

因为map的存放性质,如果在同一个ThreadLocal对象中存放多个value,那么在底层的Entry对中保存的是最近存放的value值,这也是为什么一个ThreadLocal对象只能存放一个值

线程中所有的ThreadLocal对象都被当前线程的threadLocals属性(map)管理。因此无论在哪个方法中,只要能找到对应的线程Thread,就对该线程关联的所有ThreadLocal对象中的value值进行操作。

一个线程中可以有多个ThreadLocal对象,如果还有其他ThreadLocal对象,使用set方法,存放的就是其他Entry对(key值就是其他的ThreadLocal对象)

存放多个ThreadLocal对象:

3.2ThreadLocal的get()

public T get() {
//先得到当前的线程对象
Thread t = Thread.currentThread();
//获取和当前线程对象关联的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//如果map不为空
if (map != null) {
//就根据当前的调用者this(即当前调用get方法的ThreadLocal对象),得到对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
//如果Entry的值e不为空
if (e != null) {
@SuppressWarnings("unchecked")
//返回当前ThreadLocal对象关联的value值
T result = (T)e.value;
return result;
}
}
//如果map为空,就初始化map,并将map和当前线程关联
return setInitialValue();
}

3.3总结

day36-ThreadLocal的更多相关文章

  1. ThreadLocal简单理解

    在java开源项目的代码中看到一个类里ThreadLocal的属性: private static ThreadLocal<Boolean> clientMode = new Thread ...

  2. Android线程管理之ThreadLocal理解及应用场景

    前言: 最近在学习总结Android的动画效果,当学到Android属性动画的时候大致看了下源代码,里面的AnimationHandler存取使用了ThreadLocal,激起了我很大的好奇心以及兴趣 ...

  3. Threadlocal使用Case

    Threadlocal能够为每个线程分配一份单独的副本,使的线程与线程之间能够独立的访问各自副本.Threadlocal 内部维护一个Map,key为线程的名字,value为对应操作的副本. /** ...

  4. 多线程映射工具——ThreadLocal

    ThreadLocal相当于一个Map<Thread, T>,各线程使用自己的线程对象Thread.currentThread()作为键存取数据,但ThreadLocal实际上是一个包装了 ...

  5. ThreadLocal 工作原理、部分源码分析

    1.大概去哪里看 ThreadLocal 其根本实现方法,是在Thread里面,有一个ThreadLocal.ThreadLocalMap属性 ThreadLocal.ThreadLocalMap t ...

  6. ThreadLocal<T>的是否有设计问题

    一.吐槽 ThreadLocal<T>明显是.NET从JAVA中来的一个概念,但是这种设计是否出现了问题. 很明显,在JAVA中threadLocal直接是Thread的成员,当然随着th ...

  7. 理解ThreadLocal —— 一个map的key

    作用: 当工作于多线程中的对象使用ThreadLocal维护变量时,threadLocal为每个使用该变量的线程分配一个独立的变量副本. 接口方法: protected T initialValue( ...

  8. JavaSe:ThreadLocal

    JDK中有一个ThreadLocal类,使用很方便,但是却很容易出现问题.究其原因, 就是对ThreadLocal理解不到位.最近项目中,出现了内存泄漏的问题.其中就有同事在使用ThreadLocal ...

  9. 0041 Java学习笔记-多线程-线程池、ForkJoinPool、ThreadLocal

    什么是线程池 创建线程,因为涉及到跟操作系统交互,比较耗费资源.如果要创建大量的线程,而每个线程的生存期又很短,这时候就应该使用线程池了,就像数据库的连接池一样,预先开启一定数量的线程,有任务了就将任 ...

  10. ThreadLocal 源码剖析

    ThreadLocal是Java语言提供的用于支持线程局部变量的类.所谓的线程局部变量,就是仅仅只能被本线程访问,不能在线程之间进行共享访问的变量(每个线程一个拷贝).在各个Java web的各种框架 ...

随机推荐

  1. 使用Elasticsearch的processors来对csv格式数据进行解析

    来源数据是一个csv文件,具体内容如下图所示: 导入数据到es中 有两种办法,第一种是在kibana界面直接上传文件导入 第二种方法是使用filebeat读取文件导入 这里采用第二种办法 配置文件名: ...

  2. 关于Loki中promtail组件收集日志的几点思考

    promtail组件是采用docker方式运行的,配置文件也是在docker容器中,宿主机中没有挂载点,这就有问题了. 宿主机中没有挂载配置文件,也就没法修改,登录promtail的docker容器中 ...

  3. P7800 [COCI2015-2016#6] PAROVI 方法记录

    原题链接 桔梗花于此开放 [COCI2015-2016#6] PAROVI 题目描述 \(\text{Mirko}\) 和 \(\text{Slavko}\) 在玩一个游戏,先由 \(\text{Mi ...

  4. 陆地观测卫星数据服务(CRESDA)订单ftp地址错误—已解决不能下载问题

    陆地观测卫星数据服务订单ftp地址错误 问题:本人在陆地观测卫星数据网站上申请GF1-WFV10幅数据,订单完成后返回的FTP地址出现无法连接服务器现象.(数据订单申请已通过) 一.情况介绍: ​ 我 ...

  5. 动态代理(JDK、CGLIB)

    JDK-Proxy(动态代理): 特点:要求被代理的对象必须接口 缺点:如果一个对象没有任何接口实现,则不能使用JDK动态代理 1.创建一个Animal 提供一个方法 2.创建一个cat类.实现Ain ...

  6. 洛谷 P5607 [Ynoi2013] 无力回天 NOI2017

    人生第一道Ynoi,开心 Description https://www.luogu.com.cn/problem/P5607 Solution 拿到这个题,看了一下,发现询问要求最大异或和,怎么办? ...

  7. Go | 讲解GOROOT、GOPATH、GOBIN

    前言 Go(又称 Golang)是 Google 开发的一种静态强类型.编译型.并发型,并具有垃圾回收功能的编程语言.Go 被誉为是未来的服务器端编程语言. Go是一门全新的静态类型开发语言,具有自动 ...

  8. 齐博x1如何取消某个标签的缓存时间

    标签默认会有缓存, 如果你要强制取消缓存时间的话, 可以加上下面的参数 time="-1"如下图所示 标签默认缓存时间是10分钟, 你也可以改成其它时间 比如 time=" ...

  9. 齐博x1换服务器如何转移网站?

    如果你要把网站从本机传到服务器,又或者要更换服务器,请按下面的操作处理 第一步,必须要在原网站后台备份数据. 第二步,把备份好的网站所有文件,传到新服务器或空间 特别要注意 \cache\ 目录下建议 ...

  10. 十二、Pod的NameSpace

    Pod 的 NameSpace 一.Pod 的 NameSpace 使用 kubectl 管理命名空间及其包含的资源相当简单.在这一节中,我们将演示一些最常见的命名空间操作,便于你开始有效地分割资源. ...