资讯

精准传达 • 有效沟通

从品牌网站建设到网络营销策划,从策略到执行的一站式服务

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

10. 什么是ThreadLocal

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

目前创新互联公司已为上1000家的企业提供了网站建设、域名、网络空间、网站托管运营、企业网站设计、古县网站维护等服务,公司将坚持客户导向、应用为本的策略,正道将秉承"和谐、参与、激情"的文化,与客户和合作伙伴齐心协力一起成长,共同发展。

这个玩意有什么用处?先解释一下,在并发编程的时候,一个单例模式的类的属性,如果不做任何处理(是否加锁,或者用原子类)其实是线程不安全的,各个线程都在操作同一个属性,比如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> resources = new NamedThreadLocal<>("Transactional resources");

private static final ThreadLocal> synchronizations =

new NamedThreadLocal<>("Transaction synchronizations");

private staticfinal ThreadLocal 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实例,存入数据为当前valueThreadLocal会创建一个默认长度为16Entry节点,并将k-v放入i位置(i位置计算方式和hashmap相似,当前线程的hashCode&(entry默认长度-1)),并设置阈值(默认为0)Entry默认长度的2/3

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

get()

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

11 案例:

基本案例1:

案例0

packagecom.hy.threadlocal01;

publicclassThreadLocalDemo0 {

publicstaticThreadLocal tl0= newThreadLocal();

publicstaticvoidmain(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

}

}

补充案例:匿名类

publicstaticvoidmain(String[] args) {

Empfbb= newEmp(1, "fbb", "fbb", 40);

fbb.run();

Emplbb= newEmp(2, "lbb", "lbb", 50) {

@Override

publicvoidrun() {

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

}

};

lbb.run();

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

Empzjb= newEmp(3, "zjm", "zjm", 18) {

@Override// 重写父类run方法

publicvoidrun() {

System.out.println(super.getEname() + ","+ super.getAge() + ",run...");

}

};

zjb.run();

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

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

Thread t1= newThread(newRunnable() {

@Override

publicvoidrun() {

}

});

//lambda表达式写法

Thread t2= newThread(()-> {

});

}

案例00:

packagecom.hy.threadlocal01;

publicclassThreadLocalDemo00 {

publicstaticThreadLocal tl00= newThreadLocal() {

@Override

protectedInteger initialValue() {

return100;

};

};

publicstaticvoidmain(String[] args) {

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

}

}

案例001

packagecom.hy.threadlocal01;

publicclassThreadLocalDemo001 {

publicstaticThreadLocal tl001= newThreadLocal() {

@Override

protectedInteger initialValue() {

return100;

};

};

publicstaticvoidmain(String[] args) {

tl001.set(200);

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

}

}

案例01:

packagecom.hy.threadlocal01;

publicclassThreadLocalDemo01 {

publicstaticThreadLocal tl01= newThreadLocal() {

@Override

protectedInteger initialValue() {

System.out.println("=======begin");

return100;

};

};

publicstaticvoidmain(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

packagecom.hy.threadlocal01;

publicclassThreadLocalDemo011 {

publicstaticThreadLocal tl01= newThreadLocal() {

@Override

protectedInteger initialValue() {

System.out.println("=======begin");

return100;

};

};

publicstaticvoidmain(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("***********************");

newThread() {

@Override

publicvoidrun() {

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

};

}.start();

}

}

案例0111

packagecom.hy.threadlocal01;

publicclassThreadLocalDemo0111 {

publicstaticThreadLocal tl01= newThreadLocal() {

@Override

protectedObject initialValue() {

returnnewObject();

};

};

publicstaticvoidmain(String[] args) {

finalObject o1= tl01.get();

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

newThread() {

@Override

publicvoidrun() {

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 dateFormatThreadLocal = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; // java8的写法,装逼神器 // public static ThreadLocal dateFormatThreadLocal = // ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); }

基本案例2:

案例02

packagecom.hy.threadlocal02;

publicclassThreadLocalDemo02 {

privatestaticThreadLocal tl02= newThreadLocal() {

@Override

protectedInteger initialValue() {

return0;

}

};

privatestaticvoidadd() {

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

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

Integer n= tl02.get();

n+= 1;

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

tl02.set(n);

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

}

}

publicstaticvoidmain(String[] args) {

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

newThread(newRunnable() {

@Override

publicvoidrun() {

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

packagecom.hy.db;

importjava.sql.Connection;

importjava.sql.DriverManager;

publicclassDBManager {

privatestaticfinalString URL= "jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8";

privatestaticfinalString USER= "root";

privatestaticfinalString PWD= "root";

publicstaticConnection getConn() throwsException {

Class.forName("com.mysql.jdbc.Driver");

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

returnconn;

}

publicstaticvoidmain(String[] args) throwsException {

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

}

}

新建TransactionManagerFilter

packagecom.hy.filter;

importjava.io.IOException;

importjavax.servlet.Filter;

importjavax.servlet.FilterChain;

importjavax.servlet.FilterConfig;

importjavax.servlet.ServletException;

importjavax.servlet.ServletRequest;

importjavax.servlet.ServletResponse;

importjavax.servlet.annotation.WebFilter;

@WebFilter("*.do")

publicclassTransactionManagerFilter implementsFilter {

@Override

publicvoidinit(FilterConfigfilterConfig) throwsServletException {

}

@Override

publicvoiddoFilter(ServletRequest request, ServletResponse response, FilterChain chain)

throwsIOException, ServletException {

}

@Override

publicvoiddestroy() {

}

}

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

try{

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

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

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

}catch(Exception ex){

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

}

将其封装成一个类 TransactionManager

packagecom.hy.utils;

publicclassTransactionManager {

// 开启事务

publicstaticvoidbeginTrans() {

}

// 提交事务

publicstaticvoidcommit() {

}

// 回滚事务

publicstaticvoidrollback() {

}

}

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

packagecom.hy.utils;

importjava.sql.Connection;

importcom.hy.db.DBManager;

publicclassTranscationManager {

privatestaticThreadLocal threadLocal= newThreadLocal<>();

// 开启事务

publicvoidbeginTrans() throwsException {

// 获取Connection对象

Connection conn= threadLocal.get();

if(conn== null) {

// 重新获取connecton对象

conn= DBManager.getConn();

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

threadLocal.set(conn);

}

// 设置不自动提交

conn.setAutoCommit(false);

}

// 提交事务

publicvoidcommit() throwsException {

// 获取Connection对象

Connection conn= threadLocal.get();

if(conn== null) {

// 重新获取connecton对象

conn= DBManager.getConn();

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

threadLocal.set(conn);

}

conn.commit();

}

// 回滚事务

publicvoidrollback() throwsException {

// 获取Connection对象

Connection conn= threadLocal.get();

if(conn== null) {

// 重新获取connecton对象

conn= DBManager.getConn();

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

threadLocal.set(conn);

}

conn.rollback();

}

}

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

DBManager

packagecom.hy.db;

importjava.sql.Connection;

importjava.sql.DriverManager;

publicclassDBManager {

privatestaticfinalString URL= "jdbc:mysql://localhost:3306/jspdb07?characterEncoding=utf8";

privatestaticfinalString USER= "root";

privatestaticfinalString PWD= "root";

privatestaticThreadLocal threadLocal= newThreadLocal<>();

privatestaticConnection createConn() throwsException {

Class.forName("com.mysql.jdbc.Driver");

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

returnconn;

}

publicstaticConnection getConn() throwsException {

Connection conn= threadLocal.get();

if(conn== null) {

conn= createConn();

threadLocal.set(conn);

}

returnthreadLocal.get();

}

publicstaticvoidcloseConn() throwsSQLException {

Connection conn= threadLocal.get();

if(conn== null) {

return;

}

if(!conn.isClosed()) {

conn.close();

threadLocal.set(null);

}

}

publicstaticvoidmain(String[] args) throwsException {

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

}

}

TranscationManager

packagecom.hy.utils;

importcom.hy.db.DBManager;

publicclassTranscationManager {

// 开启事务

publicvoidbeginTrans() throwsException {

DBManager.getConn().setAutoCommit(false);

}

// 提交事务

publicvoidcommit() throwsException {

DBManager.getConn().commit();

}

// 回滚事务

publicvoidrollback() throwsException {

DBManager.getConn().rollback();

}

}

新TransactionManager

packagecom.hy.utils;

importcom.hy.db.DBManager;

publicclassTransactionManager {

// 开启事务

publicstaticvoidbeginTrans() throwsException {

DBManager.getConn().setAutoCommit(false);

}

// 提交事务

publicstaticvoidcommit() throwsException {

DBManager.getConn().commit();

DBManager.closeConn();

}

// 回滚事务

publicstaticvoidrollback() throwsException {

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的结构。

我们通过调用ThreadLocalset方法将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掉。

2set方法源码

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)如果mapnull,说明首次添加,需要首先创建出对应的map

else

createMap(t, value);

}

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

ThreadLocalMap getMap(Thread t) {

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

}

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

1voidcreateMap(Thread t, T firstValue) {

2t.threadLocals = newThreadLocalMap(this, firstValue);

3}

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

3get方法源码

  在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)执行到此处,threadLocalsnull,调用该更改初始化当前线程的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);

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

else

createMap(t, value);

return value;

}

4remove方法的实现

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

public void remove() {

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

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


网站名称:六,手写SpringMVC框架--什么是ThreadLocal?
浏览路径:http://cdkjz.cn/article/dscgioc.html
多年建站经验

多一份参考,总有益处

联系快上网,免费获得专属《策划方案》及报价

咨询相关问题或预约面谈,可以通过以下方式与我们联系

大客户专线   成都:13518219792   座机:028-86922220