六,手写SpringMVC框架--什么是ThreadLocal?
10. 什么是ThreadLocal
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<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); |
Spring的事务主要是ThreadLocal和AOP去做实现的,我这里提一下,大家知道每个线程自己的Connection conn是靠ThreadLocal保存的就好了。
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的数量。
ThreadLocal常用方法:
ThreadLocal在案例中一般以static形式存在的。
initialValue方法
此方法为ThreadLocal保存的数据类型指定的一个初始化值,在ThreadLocal中默认返回null。但可以重写initialValue()方法进行数据初始化。
如果使用的是Java8提供的Supplier函数接口更加简化:
set(T value)方法
get方法
get()用于返回当前线程ThreadLocal中数据备份,当前线程的数据都存在一个ThreadLocalMap的数据结构中。
remomve()删除值
小结
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方法一样的方法。
11 案例:
基本案例1:
案例0:
package com.hy.threadlocal01;
public class ThreadLocalDemo0 { public static ThreadLocal<Integer> tl0 = new ThreadLocal<Integer>();
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(()-> {
}); } |
案例00:
package com.hy.threadlocal01;
public class ThreadLocalDemo00 { public static ThreadLocal<Integer> tl00 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 100; }; };
public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+":"+tl00.get()); } } |
案例001:
package com.hy.threadlocal01;
public class ThreadLocalDemo001 { public static ThreadLocal<Integer> tl001 = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { return 100; }; };
public static void main(String[] args) { tl001.set(200); System.out.println(Thread.currentThread().getName()+":"+tl001.get()); } } |
案例01:
package com.hy.threadlocal01;
public class ThreadLocalDemo01 { public static ThreadLocal<Integer> tl01 = new ThreadLocal<Integer>() { @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()); } } |
案例011
package com.hy.threadlocal01;
public class ThreadLocalDemo011 { public static ThreadLocal<Integer> tl01 = new ThreadLocal<Integer>() { @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<Object> tl01 = new ThreadLocal<Object>() { @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<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; // java8的写法,装逼神器 // public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = // ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); }
|
基本案例2:
案例02:
package com.hy.threadlocal02;
public class ThreadLocalDemo02 { private static ThreadLocal<Integer> tl02 = new ThreadLocal<Integer>() { @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 对象)作为参数传递的麻烦
ThreadLocal的其他使用场景场景(面试加分项)
除了源码里面使用到ThreadLocal的场景,你自己有使用他的场景么?一般你会怎么用呢?
之前我们项目上线后发现部分用户的日期居然不对了,排查下来是SimpleDataFormat的锅,当时我们使用SimpleDataFormat的parse()方法,内部有一个Calendar对象,调用SimpleDataFormat的parse()方法会先调用Calendar.clear(),然后调用Calendar.add(),如果一个线程先调用了add()然后另一个线程又调用了clear(),这时候parse()方法解析的时间就不对了。 其实要解决这个问题很简单,让每个线程都new 一个自己的 SimpleDataFormat就好了,但是1000个线程难道new1000个SimpleDataFormat? 所以当时我们使用了线程池加上ThreadLocal包装SimpleDataFormat,再调用initialValue让每个线程有一个SimpleDataFormat的副本,从而解决了线程安全的问题,也提高了性能。 |
新建DBManager
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 static void beginTrans() { }
// 提交事务 public static void commit() { }
// 回滚事务 public static void rollback() { } } |
现在问题的焦点来到了,如何在TranscationManager中获取Connection对象,当然可以在方法中传递Connection对象,但是这是面向对象的方式。
package com.hy.utils;
import java.sql.Connection;
import com.hy.db.DBManager;
public class TranscationManager { private static ThreadLocal<Connection> threadLocal = new 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对象。所以要想办法将这几句代码放入到DBManager当中。
新DBManager
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<Connection> threadLocal = new 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(); } } |
部分源码:
ThreadLocal解析:
是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的属性。
我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。
在默认情况下,每个线程对象都有两个属性,但是这两个属性量都为null
只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。
除此之外,和我所想的不同的是,每个线程的本地变量的值 不是存放在ThreadLocal对象中,而是放在调用的线程对象的threadLocals属性里面(前面也说过,threadLocals是Thread类的属性)。也就是说,
ThreadLocal类 其实相当于一个 管家一样(所谓的工具人),只是用来 存值/取值 的,但是 存的值/取的值都来自于 当前线程对象里 threadLocals属性,而这个属性是一个类似于Map的结构。
我们通过调用ThreadLocal的set方法将value值 添加到调用线程的threadLocals中,
通过调用ThreadLocal的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); } |