六,手写SpringMVC框架–什么是ThreadLocal?

1 0. 什么是Thread L ocal

ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。或称为 线程本地变量

这个玩意有什么用处?先解释一下,在并发编程的时候,一个单例模式的类的属性,如果不做任何处理(是否加锁,或者用原子类)其实是线程不安全的,各个线程都在操作同一个属性,比如CoreServlet,Servlet是单例模式,所以如果在Servlet中增加一个属性,那么就会有多线程访问这个属性就会诱发的安全性问题。

这样显然是不行的,并且我们也知道volatile这个关键字只能保证线程的可见性,不能保证线程安全的。如果加锁,效率有会有一定程度的降低。

那么我们需要满足这样一个条件:属性是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,DAO我们在实际项目中都会是单例模式的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。

ThreadLocal 的主要作用:

ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。

Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。

Spring框架里面就是用的ThreadLocal来实现这种隔离,主要是在TransactionSynchronizationManager这个类里面,代码如下所示:

private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

private static final ThreadLocal

private static final ThreadLocal

new NamedThreadLocal<>(“Transaction synchronizations”);

private static final ThreadLocal

new NamedThreadLocal<>(“Current transaction name”);

Spring的事务主要是ThreadLocal和AOP去做实现的,我这里提一下,大家知道每个线程自己的Connection conn 是靠ThreadLocal保存的就好了。

Thread L ocal 结构图:

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

当ThreadLocal Ref出栈后,由于ThreadLocalMap中Entry对ThreadLocal只是弱引用,所以ThreadLocal对象会被回收,Entry的key会变成null,然后在每次get/set/remove ThreadLocalMap中的值的时候,会自动清理key为null的value,这样value也能被回收了。

注意:如果ThreadLocal Ref一直没有出栈(例如上面的connectionHolder,通常我们需要保证ThreadLocal为单例且全局可访问,所以设为static),具有跟Thread相同的生命周期,那么这里的虚引用便形同虚设了,所以使用完后记得调用ThreadLocal.remove将其对应的value清除。

另外,由于ThreadLocalMap中只对ThreadLocal是弱引用,对value是强引用,如果ThreadLocal因为没有其他强引用而被回收,之后也没有调用过get/set,那么就会产生内存泄露,

在使用线程池时,线程会被复用,那么里面保存的ThreadLocalMap同样也会被复用,会造成线程之间的资源没有被隔离,所以在线程归还回线程池时要记得调用remove方法。

hash冲突

上面提到ThreadLocalMap是自己实现的类似HashMap的功能,当出现Hash冲突(通过两个key对象的hash值计算得到同一个数组下标)时,它没有采用链表模式,而是采用的线性探测的方法,既当发生冲突后,就线性查找数组中空闲的位置。

当数组较大时,这个性能会很差,所以建议尽量控制ThreadLocal的数量。

Thread L ocal 常用方法:

ThreadLocal 在案例中一般以static 形式存在的。

initialValue 方法

六,手写SpringMVC框架--什么是ThreadLocal?

此方法为ThreadLocal 保存的数据类型指定的一个初始化值,在ThreadLocal 中默认返回null 。但可以重写initialValue()方法进行数据初始化。
如果使用的是Java8 提供的Supplier 函数接口更加简化:

六,手写SpringMVC框架--什么是ThreadLocal?

set(T value)方法

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

get 方法

get()用于返回当前线程ThreadLocal 中数据备份,当前线程的数据都存在一个ThreadLocalMap 的数据结构中。

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

rem omve ()删除值

小结

initialValue() : 初始化ThreadLocal 中的value 属性值。

set():获取当前线程,根据当前线程从ThreadLocals 中获取ThreadLocalMap 数据结构,

如果ThreadLocalmap 的数据结构没创建,则创建ThreadLocalMap,key 为当前ThreadLocal 实例,存入数据为当前value 。ThreadLocal 会创建一个默认长度为16Entry 节点,并将k-v 放入i 位置(i 位置计算方式和hashmap 相似,当前线程的hashCode&(entry 默认长度-1)),并设置阈值(默认为0)为Entry 默认长度的2/3 。

如果ThreadLocalMap 存在。就会遍历整个Map 中的Entry 节点,如果entry 中的key 和本线程ThreadLocal 相同,将数据(value)直接覆盖,并返回。如果ThreadLoca 为null ,驱除ThreadLocal 为null 的Entry ,并放入Value ,这也是内存泄漏的重点地区。

get()

get()方法比较简单。就是根据Thread 获取ThreadLocalMap 。通过ThreadLocal 来获得数据value 。注意的是:如果ThreadLocalMap 没有创建,直接进入创建过程。初始化ThreadLocalMap 。并直接调用和set 方法一样的方法。

1 1 案例:

基本案例1 :

案例0 :

package com.hy.threadlocal01;

public class ThreadLocalDemo0 {

public static ThreadLocal

public static void main(String[] args) {

System. out.println(Thread. currentThread().getName() + “:” + tl0.get()); // main:null

tl0.set(1000);

System. out.println(Thread. currentThread().getName() + “:” + tl0.get()); //main:1000

}

}

六,手写SpringMVC框架--什么是ThreadLocal?

补充案例:匿名类

public static void main(String[] args) {

Emp fbb = new Emp(1, “fbb”, “fbb”, 40);

fbb.run();

Emp lbb = new Emp(2, “lbb”, “lbb”, 50) {

@Override

public void run() {

super.run(); // 调用父类的run 方法

}

};

lbb.run();

//new 了一个类的对象,这个类是一个匿名类,但是我知道这个类继承/实现了Emp 类

Emp zjb = new Emp(3, “zjm”, “zjm”, 18) {

@Override // 重写父类run 方法

public void run() {

System. out.println(super.getEname() + “,” + super.getAge() + “,run…”);

}

};

zjb.run();

//和下面这案例,不能说完全相同,只能说一模一样

//new 了一个匿名类该匿名类实现了Runnable 接口

Thread t1 = new Thread(new Runnable() {

@Override

public void run() {

}

});

//lambda 表达式写法

Thread t2 = new Thread(()-> {

});

}

案例0 0 :

package com.hy.threadlocal01;

public class ThreadLocalDemo00 {

public static ThreadLocal

@Override

protected Integer initialValue() {

return 100;

};

};

public static void main(String[] args) {

System. out.println(Thread. currentThread().getName()+”:”+ tl00.get());

}

}

六,手写SpringMVC框架--什么是ThreadLocal?

案例0 01

package com.hy.threadlocal01;

public class ThreadLocalDemo001 {

public static ThreadLocal

@Override

protected Integer initialValue() {

return 100;

};

};

public static void main(String[] args) {

tl001.set(200);

System. out.println(Thread. currentThread().getName()+”:”+ tl001.get());

}

}

六,手写SpringMVC框架--什么是ThreadLocal?

案例0 1 :

package com.hy.threadlocal01;

public class ThreadLocalDemo01 {

public static ThreadLocal

@Override

protected Integer initialValue() {

System. out.println(“=======begin”);

return 100;

};

};

public static void main(String[] args) {

System. out.println(Thread. currentThread().getName() + “: ->get -> init:” + tl01.get());

tl01.set(200); // main 线程改成200;

System. out.println(Thread. currentThread().getName() + “: ->set -> get:” + tl01.get());

tl01.remove();

System. out.println(Thread. currentThread().getName() + “: -> remove -> get->init:” + tl01.get());

tl01.get();

System. out.println(Thread. currentThread().getName() + “: -> get:” + tl01.get());

}

}

六,手写SpringMVC框架--什么是ThreadLocal?

案例0 11

package com.hy.threadlocal01;

public class ThreadLocalDemo011 {

public static ThreadLocal

@Override

protected Integer initialValue() {

System. out.println(“=======begin”);

return 100;

};

};

public static void main(String[] args) {

System. out.println(Thread. currentThread().getName()+”:”+ tl01.get());

tl01.set(200); //main 线程改成200;

System. out.println(Thread. currentThread().getName()+”:”+ tl01.get());

System. out.println(“*****”);

new Thread() {

@Override

public void run() {

System. out.println(Thread. currentThread().getName()+”:”+ tl01.get());

};

}.start();

}

}

六,手写SpringMVC框架--什么是ThreadLocal?

案例 0111

package com.hy.threadlocal01;

public class ThreadLocalDemo0111 {

public static ThreadLocal

@Override

protected Object initialValue() {

return new Object();

};

};

public static void main(String[] args) {

final Object o1 = tl01.get();

System. out.println(Thread. currentThread().getName() + “:” + o1);

new Thread() {

@Override

public void run() {

Object o2 = tl01.get();

System. out.println(Thread. currentThread().getName() + “:” + o2);

System. out.println(o1 == o2);

};

}.start();

}

}

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

案例2 :

public class ThreadLocalTest05 {

public static String dateToStr(int millisSeconds) {

Date date = new Date(millisSeconds);

SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();

return simpleDateFormat.format(date);

}

private static final ExecutorService executorService = Executors.newFixedThreadPool(100);

public static void main(String[] args) {

for (int i = 0; i < 3000; i++) {

int j = i;

executorService.execute(() -> {

String date = dateToStr(j * 1000); // 从结果中可以看出是线程安全的,时间没有重复的。 System.out.println(date); }); } executorService.shutdown(); } } class ThreadSafeFormatter { public static ThreadLocal

基本案例2:

案例0 2

package com.hy.threadlocal02;

public class ThreadLocalDemo02 {

private static ThreadLocal

@Override

protected Integer initialValue() {

return 0;

}

};

private static void add() {

for (int i = 0; i < 5; i++) {

// 从当前线程的ThreadLocal 中获取默认值

Integer n = tl02.get();

n += 1;

// 往当前线程的ThreadLocal 中设置值

tl02.set(n);

System. out.println(Thread. currentThread().getName() + ” : ThreadLocal num=” + n);

}

}

public static void main(String[] args) {

for (int i = 0; i < 3; i++) {

new Thread(new Runnable() {

@Override

public void run() {

add();

}

}).start();

}

}

}

保证每个线程都能遍历完成,并且数据正确,其他线程不会影响当前线程的数据。

六,手写SpringMVC框架--什么是ThreadLocal?

典型场景1 :

通常用于保存线程不安全的工具类,典型的需要使用的类就是 SimpleDateFormat 。

景介绍

在这种场景下,每个 Thread 内都有自己的实例副本,且该副本只能由当前 Thread 访问到并使用,相当于每个线程内部的本地变量,这也是 ThreadLocal 命名的含义。因为每个线程独享副本,而不是公用的,所以不存在多线程;间共享的问题。

我们来做一个比喻,比如饭店要做一道菜,但是有 5 个厨师一起做,这样的话就很乱了,因为如果一个厨师已经放过盐了,假如其他厨师都不知道,于是就都各自放了一次盐,导致最后的菜很咸。这就好比多线程的情况,线程不安全。我们用了 ThreadLocal 之后,相当于每个厨师只负责自己的一道菜,一共有 5 道菜,这样的话就非常清晰明了了,不会出现问题。

典型场景2

使用 ThreadLocal的好处,无非就是,同一个线程无需通过 方法参数传递变量,因为变量是线程持有的,所以想用就可以直接用。

业务场景的例子

一个request请求进入tomcat容器, 进入controller, 再进入service, 再进入dao, 可能还会向自定义线程池发一个异步任务

在这么多的类的方法中我想用某些共享的变量怎么办?

以userId 为例:

  • service 方法用 userId _id 判断用户权限
  • dao 方法用 userId 在表中存储数据修改人的信息
  • 异步调用另一个服务B 的时候, 让 B 知道是谁调用了他
  • 所有方法打印的log, 我想统一加上 userId ,否则不知道是谁调用的, 但是这么多方法改起来是是很崩溃的

以上所有方法, 如果都加上 String userId 作为参数有多丑陋不用我说大家也能想到, 即使你都加上了, 那么以后又多了一个字段你咋办? 再全改一遍吗?

spring 的例子:

TransactionSynchronizationManager

spring的事务是可以嵌套的, 可能是10个service方法属于一个事务, 如果没有这个机制那么所有方法签名都要加上 Connection connection 作为参数

RequestContextHolder

在任何地方都可以得到 request 请求的参数, 但是这个容易滥用, 导致不同层的代码耦合在一起, 如果你在 service 方法中用了他, 那么你的 service 方法就无法很方便的单元测试, 因为你耦合了 http 请求的一些东西, 这本身应该是 controller 关注的

以上例子都是在一个Thread 内是ok 的,如果新生成一个Thread,这些变量咋带过去呢?不带过去不就失联了吗?

比如异步调用发短信服务, 短信服务想知道user_id是谁, 那么加方法参数依然是丑陋的

好在 jdk 给我们解决了一部分也就是, 如果用的是InheritableThreadLocal 那么在new Thread()的时候会复制这些变量到新线程, 但是如果你用的线程池就搞不定了

因为线程池中的线程初期是 new Thread 可以将变量带过去, 后期就不会 new Thread了, 而是从 pool 中直接拿一个 thread, 也就触发不了这一步了, 因此需要用到阿里开源的一个框架 transmittable-thread-local 来改造线程池来支持tl的变量传递。

=====================================================

每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。

例如,用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。

在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 过的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦

Thread L ocal 的其他使用场景场景( 面试加分项)

除了源码里面使用到ThreadLocal的场景,你自己有使用他的场景么?一般你会怎么用呢?

之前我们项目上线后发现部分用户的日期居然不对了,排查下来是SimpleDataFormat的锅,当时我们使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了。

其实要解决这个问题很简单,让每个线程都new 一个自己的 SimpleDataFormat就好了,但是1000个线程难道new1000个SimpleDataFormat?

所以当时我们使用了线程池加上ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程有一个SimpleDataFormat的副本,从而解决了线程安全的问题,也提高了性能。

新建D BM anager

package com.hy.db;

import java.sql.Connection;

import java.sql.DriverManager;

public class DBManager {

private static final String URL = “jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8”;

private static final String USER = “root”;

private static final String PWD = “root”;

public static Connection getConn() throws Exception {

Class. forName(“com.mysql.jdbc.Driver”);

Connection conn = DriverManager. getConnection(URL, USER, PWD);

return conn;

}

public static void main(String[] args) throws Exception {

System. out.println(DBManager. getConn());

}

}

新建TransactionManagerFilter

package com.hy.filter;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.annotation.WebFilter;

@WebFilter(“*.do”)

public class TransactionManagerFilter implements Filter {

@Override

public void init(FilterConfig filterConfig) throws ServletException {

}

@Override

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throws IOException, ServletException {

}

@Override

public void destroy() {

}

}

事务管理过滤器中要写如下的代码:开启事务,提交事务,回滚事务

try{

conn.setAutoCommit(false); //开启事务

chain.doFilter(req,resp);// 放行();

conn.commit(); //提交事务

}catch(Exception ex){

conn.rollback(); //回滚事务

}

将其封装成一个类 TransactionManager

package com.hy.utils;

public class TransactionManager {

// 开启事务

public s tatic void beginTrans() {

}

// 提交事务

public s tatic void commit() {

}

// 回滚事务

public s tatic void rollback() {

}

}

现在问题的焦点来到了,如何在TranscationManager 中获取Connection 对象,当然可以在方法中传递Connection 对象,但是这是面向对象的方式。

package com.hy.utils;

import java.sql.Connection;

import com.hy.db.DBManager;

public class TranscationManager {

private static ThreadLocal

// 开启事务

public void beginTrans() throws Exception {

// 获取Connection 对象

Connection conn = threadLocal.get();

if (conn == null) {

// 重新获取connecton 对象

conn = DBManager. getConn();

// 将Connection 对象放在ThreadLocal 操作的map 中。

threadLocal.set(conn);

}

// 设置不自动提交

conn.setAutoCommit(false);

}

// 提交事务

public void commit() throws Exception {

// 获取Connection 对象

Connection conn = threadLocal.get();

if (conn == null) {

// 重新获取connecton 对象

conn = DBManager. getConn();

// 将Connection 对象放在ThreadLocal 操作的map 中。

threadLocal.set(conn);

}

conn.commit();

}

// 回滚事务

public void rollback() throws Exception {

// 获取Connection 对象

Connection conn = threadLocal.get();

if (conn == null) {

// 重新获取connecton 对象

conn = DBManager. getConn();

// 将Connection 对象放在ThreadLocal 操作的map 中。

threadLocal.set(conn);

}

conn.rollback();

}

}

大家会发现,在这三个方法中,黄色代码部分都是一样的。这个代码的目的就是获取Connection 对象。所以要想办法将这几句代码放入到DBM anager 当中。

新D BM anager

package com.hy.db;

import java.sql.Connection;

import java.sql.DriverManager;

public class DBManager {

private static final String URL = “jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8”;

private static final String USER = “root”;

private static final String PWD = “root”;

private static ThreadLocal

private static Connection createConn() throws Exception {

Class. forName(“com.mysql.jdbc.Driver”);

Connection conn = DriverManager. getConnection(URL, USER, PWD);

return conn;

}

public static Connection getConn() throws Exception {

Connection conn = threadLocal.get();

if(conn == null) {

conn = createConn();

threadLocal.set(conn);

}

return threadLocal.get();

}

public static void closeConn() throws SQLException {

Connection conn = threadLocal.get();

if(conn == null) {

return;

}

if(!conn.isClosed()) {

conn.close();

threadLocal.set(null);

}

}

public static void main(String[] args) throws Exception {

System. out.println(DBManager. getConn());

}

}

TranscationManager

package com.hy.utils;

import com.hy.db.DBManager;

public class TranscationManager {

// 开启事务

public void beginTrans() throws Exception {

DBManager. getConn().setAutoCommit(false);

}

// 提交事务

public void commit() throws Exception {

DBManager. getConn().commit();

}

// 回滚事务

public void rollback() throws Exception {

DBManager. getConn().rollback();

}

}

新TransactionManager

package com.hy.utils;

import com.hy.db.DBManager;

public class TransactionManager {

// 开启事务

public static void beginTrans() throws Exception {

DBManager. getConn().setAutoCommit(false);

}

// 提交事务

public static void commit() throws Exception {

DBManager. getConn().commit();

DBManager. closeConn();

}

// 回滚事务

public static void rollback() throws Exception {

DBManager. getConn().rollback();

DBManager. closeConn();

}

}

部分源码:

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

六,手写SpringMVC框架--什么是ThreadLocal?

T hread L ocal 解析:

是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的属性。

我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。

在默认情况下,每个线程对象都有两个属性,但是这两个属性量都为null

只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。

除此之外,和我所想的不同的是,每个 线程的本地变量的值不是存放在ThreadLocal对象中,而是放在调用的线程对象的threadLocals属性里面(前面也说过,threadL ocals是Thread类的属性)。也就是说,

ThreadLocal类 其实相当于一个 管家一样(所谓的工具人),只是用来 存值/取值 的,但是 存的值/取的值都来自于 当前线程对象里threadLocals 属性,而这个属性是一个类似于Map的结构。

我们通过调用ThreadLocal 的set方法将value值 添加到调用线程的threadLocals中,

通过调用T hreadL ocal的get方法,它能够从它的当前线程的threadLocals中取出该值。

如果调用线程一直不终止,那么这个值(本地变量的值)将会一直存放在当前线程对象的threadLocals 中。

当不使用本地变量的时候(也就是那个值时),需要只调用工具人ThreadLocal 的 remove方法将其从当前线程对象的threadLocals中删除即可。

下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的

1、解析:

六,手写SpringMVC框架--什么是ThreadLocal?

每个线程内部有一个名为threadLocals的属性,该属性的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。

六,手写SpringMVC框架--什么是ThreadLocal?

2 、set 方法源码

public void set(T value) {

//(1)获取当前线程(调用者线程)

Thread t = Thread.currentThread();

//(2)以当前线程作为key 值,去查找对应的线程变量,找到对应的map

ThreadLocalMap map = getMap(t);

//(3)如果map 不为null ,就直接添加本地变量, key 为当前定义的ThreadLocal 变量的this 引用,值为添加的本地变量值

if (map != null)

map.set(this, value);

//(4)如果map 为null ,说明首次添加,需要首先创建出对应的map

else

createMap(t, value);

}

在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下

ThreadLocalMap getMap(Thread t) {

return t.threadLocals; //获取线程自己的变量threadLocals ,并绑定到当前调用线程的成员变量threadLocals 上

}

如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示

1 void createMap(Thread t, T firstValue) {

2 t.threadLocals = new ThreadLocalMap(this, firstValue);

3 }

createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

3 、get 方法源码

在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。

public T get() {

//(1)获取当前线程

Thread t = Thread.currentThread();

//(2)获取当前线程的threadLocals 变量

ThreadLocalMap map = getMap(t);

//(3)如果threadLocals 变量不为null ,就可以在map 中查找到本地变量的值

if (map != null) {

ThreadLocalMap.Entry e = map.getEntry(this);

if (e != null) {

@SuppressWarnings(“unchecked”)

T result = (T)e.value;

return result;

}

}

//(4)执行到此处,threadLocals 为null ,调用该更改初始化当前线程的threadLocals 变量

return setInitialValue();

}

private T setInitialValue() {

//protected T initialValue() {return null;}

T value = initialValue();

//获取当前线程

Thread t = Thread.currentThread();

//以当前线程作为key 值,去查找对应的线程变量,找到对应的map

ThreadLocalMap map = getMap(t);

//如果map 不为null ,就直接添加本地变量,key 为当前线程,值为添加的本地变量值

if (map != null)

map.set(this, value);

//如果map 为null ,说明首次添加,需要首先创建出对应的map

else

createMap(t, value);

return value;

}

4 、remove 方法的实现

remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量

public void remove() {

//获取当前线程绑定的threadLocals

ThreadLocalMap m = getMap(Thread.currentThread());

//如果map 不为null ,就移除当前线程中指定ThreadLocal 实例的本地变量

if (m != null)

m.remove(this);

}

Original: https://www.cnblogs.com/lijili/p/16596676.html
Author: 雾里看瓜
Title: 六,手写SpringMVC框架–什么是ThreadLocal?

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/622275/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【ZooKeeper系列】2.用Java实现ZooKeeper API的调用

    &#x6E29;&#x99A8;&#x63D0;&#x793A;:在这里我再次提个小要求,希望大家能习惯看 官方文档,文档虽然是英文但用词都比较简单…

    Java 2023年5月29日
    069
  • redis主从、哨兵主备切换搭建一步一步图解实现

    Redis支持主从复用。数据可以从主服务器向任意数量的从服务器上同步,同步使用的是发布/订阅机制。Mater Slave的模式,从Slave向Master发起SYNC命令。 可以是…

    Java 2023年6月5日
    089
  • MQTT研究之EMQ:【EMQX使用中的一些问题记录(2)】

    我的测试环境: Linux: CentOS7EMQX:V3.2.3 题外话: 这里主要介绍Websocket的支持问题。 对ws的支持比较正常,但是对wss的支持,调了较长的时间,…

    Java 2023年5月30日
    070
  • Java基础之Synchronized原理

    思维导图svg: https://note.youdao.com/ynoteshare1/index.html?id=eb05fdceddd07759b8b82c5b9094021…

    Java 2023年5月29日
    075
  • 华为暑期实习 通用软件开发 面经

    华为暑期实习 通用软件开发工程师 数据存储与机器视觉 面经 机试 7.6 第一题 字符串匹配 给五行英文句子,找出来其中的网址,网址以http或https开头,以com结尾,不要重…

    Java 2023年6月5日
    0100
  • 头歌计组实践

    头歌计组实践 一.关于用什么写Verilog Verilog是个硬件描述语言,不像高级语言用户群体那么大,所以好像没有像什么IDE那样的东西,个人目前了解有以下这么几个形式: 文本…

    Java 2023年6月7日
    074
  • Maven还停留在导jar包?快来探索Nexus私服的新世界

    写在前面 Maven,学习框架之前我们都会接触到的一个工具,感觉他的定位,似乎就跟git一样,只是方便我们开发?于是自然而然的,很多小猿对于Maven都只是停留在会用的阶段,利用他…

    Java 2023年6月5日
    097
  • JVM类加载机制

    JVM类加载机制 JVM类加载机制分为:加载,验证,准备,解析,初始化五步,如 下图: 加载:这个阶段会在内存中生成一个代表这个类的java.lang.Class对象作为方法区这个…

    Java 2023年6月7日
    047
  • Idea2019.3 :一直卡在Resolving Maven dependencies

    maven仓库是阿里的 问题 如图,下载jar包挺快,一直卡在解析那一步。。。。导致写注解老是爆红 解决 修改maven Importing的jvm参数, 默认为700多, 直接修…

    Java 2023年6月7日
    069
  • AnotherRedisDesktopManager下载安装与连接Redis数据库

    一、AnotherRedisDesktopManager下载 作者在gitee与github上皆提供了下载 gitee下载地址:AnotherRedisDesktopManager…

    Java 2023年6月8日
    077
  • springboot中@Configuration的用法

    一、背景在spring框架中,会有大量 的xml配置文件,或者需要做很多繁琐的配置。 从spring3开始,spring就支持了两种bean的配置方式, 一种是基于xml文件方式、…

    Java 2023年5月30日
    064
  • Object.keys() 作用

    能便利 Key Object.keys() posted @2022-08-31 16:04 简单易懂 阅读(7 ) 评论() 编辑 Original: https://www.c…

    Java 2023年6月5日
    076
  • Tomcat和Nginx的区别

    Tomcat和Nginx的区别 1、从应用方面 tomcat一般是做动态解析才会用得到,支持jsp的解析,需要配置JDK支持。 nginx,则一般是做静态,本身不具备动态解析功能,…

    Java 2023年6月15日
    063
  • SpringMVC转发和重定向区别!

    在servlet中,转发和重定向是由request和response完成的。两者之间的区别请看我之前的文章。那么在springMVC中是如何完成的呢? /转发/ @RequestM…

    Java 2023年6月9日
    0168
  • [Java] 封装zip内文件处理的函数,演示修改zip内的txt追加文本

    作者: zyl910 一、缘由 上一篇文章演示了无需解压的替换zip内文件的技术原理。本文准备编写一个实际的例子——演示修改zip内的txt文件,在后面追加文本。 二、封装zip内…

    Java 2023年5月29日
    097
  • JDK成长记10:Thread的基本原理和常见应用场景,你都知道么?

    相信你经过集合篇的成长,已经对JDK源码的学习轻车熟路了。接下来你将一起和我进入后半篇的学习。让我们开始吧! 在接下来10分钟,你将学习到thread 的源码原理、线程的状态变化、…

    Java 2023年6月5日
    082
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球