一.概念
定义:
ThreadLocal 是 Java 中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据。
其实是可以通过调用 Set() 方法往里面存入值,存入的值是每个线程互相隔离、互不影响的,每个线程都有一个 ThreadLocal 的空间来存 ThreadLocal 的数据,那么它是怎么做到每个线程互相隔离、互不影响的呢?这就需要从底层数据结构分析了。
二.底层数据结构
先看下图:
1.在线程上,有一个叫ThreadLocalMap的变量,这个变量用来存储当前线程的ThreadLocal的数据,ThreadLocalMap里面可能有多个entry对象,每个entry都由一个Key-Value组成;
2.当调用threadLocal.set()方法的时候,其实就是先拿到当前线程中的ThreadLocalMap,拿到以后再将这个threadLocal放到entry的key上面,再将这个存储的值放在value里面;
3.在当前线程中使用了多个threadLocal时,会存储多个entry,并且这些entry都会绑定当前线程。
通过上诉的流程,就达到了每个线程的相互隔离、互不影响。
接下来再通过源码进行查看:
ThreadLocal.ThreadLocalMap threadLocals = null;public void set(T value) {// 获取当前线程Thread t = Thread.currentThread();// 获取当前线程的ThreadLocalMap对象ThreadLocalMap map = getMap(t);// 不为空就直接插入当前ThreadLocalMap中if (map != null)map.set(this, value);elsecreateMap(t, value);}ThreadLocal.ThreadLocalMap getMap(Thread t) {return t.threadLocals;}
其实可以理解ThreadLocal其实是用来操作当前线程中的ThreadLocalMap的一个工具类,通过上诉的set()方法,就可以实现每个线程的相互隔离、互不影响。
总结:
ThreadLocal底层是通过ThreadLocalMap来实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值。
三.使用注意事项
如果在线程池中使用ThreadLocal会造成内存泄漏,因为当ThreadLocal对象使用完之后,应该要把Entry对象(设置的key-value)进行回收,但线程池中的核心线程是不会被回收的,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏,解决办法是,在使用了ThreadLocal对象之后,手动调用ThreadLocal的remove方法,手动清除Entry对象。
四.应用场景
ThreadLocal的使用场景很多,但是它都遵守一个原则:当一个共享变量是共享的,但是需要每个线程互不影响,相互隔离,就可以使用ThreadLocal。
日常开发常用的两种场景:
1.跨层传递信息的时候,每个方法都声明一个参数很麻烦, A\B\C\D 4个类互相传递每个方法都声明参数降低了维护性,可以用一个ThreadLocal共享变量,在A存值,BCD都可以获取;(比如有一个User需要在ABCD类中传递,那么4个类都需要定义User,并且当User的参数修改后,4个类都需要修改,这也不方便进行维护,所以可以用一个ThreadLocal共享变量来实现)
2.隔离线程,存储一些线程不安全的工具对象(如SimpleDateFormat,在多线程的场景下去做日期的格式化,就可能出现线程不安全的问题);
框架中的使用:
1.Spring中的事务管理器就是使用的ThreadLocal;(在多线程情况下,声明式事务是没办法达到一致性的,因为一个线程就存储了一个事务)
2.Springmvc的HttpSession、HttpServletReugest、HttpServletResponse都是放在ThreadLocal,因为servlet是单例的,而springmvc允许在controller类中通过@Autowired配置request、response以及requestcontext等实例对象,底层就是搭配Threadlocal才实现线程安全。(request、response如果是单列的,就会出现线程安全问题,因为每个请求都有自己的request和response,所以它们的底层都是通过ThreadLocal也进行存储的这些对象的)