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 结构图:
当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 方法
此方法为ThreadLocal 保存的数据类型指定的一个初始化值,在ThreadLocal 中默认返回null 。但可以重写initialValue()方法进行数据初始化。
如果使用的是Java8 提供的Supplier 函数接口更加简化:
set(T value)方法
get 方法
get()用于返回当前线程ThreadLocal 中数据备份,当前线程的数据都存在一个ThreadLocalMap 的数据结构中。
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
}
}
补充案例:匿名类
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());
}
}
案例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());
}
}
案例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());
}
}
案例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();
}
}
案例 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();
}
}
案例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();
}
}
}
保证每个线程都能遍历完成,并且数据正确,其他线程不会影响当前线程的数据。
典型场景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();
}
}
部分源码:
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、解析:
每个线程内部有一个名为threadLocals的属性,该属性的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
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/
转载文章受原作者版权保护。转载请注明原作者出处!