ThreadLocal
是JDK
包提供的,从名字来看,ThreadLocal
意思就是本地线程的意思。html
要想知道他是个啥,咱们看看ThreadLocal
的源码(基于JDK 1.8
)中对这个类的介绍:java
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
大体可以总结出:git
TreadLocal
能够给咱们提供一个线程内的局部变量,并且这个变量与通常的变量还不一样,它是每一个线程独有的,与其余线程互不干扰的;ThreadLocal
与普通变量的区别在于:每一个使用该变量的线程都会初始化一个彻底独立的实例副本。ThreadLocal
变量一般被private static
修饰。当一个线程结束时,它所使用的全部 ThreadLocal
相对的实例副本都会被回收;ThreadLocal
就是一种以空间换时间的作法,在每一个Thread
里面维护了一个ThreadLocal.ThreadLocalMap
,把数据进行隔离,每一个线程的数据不共享,天然就没有线程安全方面的问题了.一言不合上代码!github
//建立ThreadLocal变量 private static ThreadLocal<String> localParam = new ThreadLocal<>(); @Test public void threadLocalDemo() { //建立2个线程,分别设置不一样的值 new Thread(() -> { localParam.set("Hello 风尘博客!"); //打印当前线程本地内存中的localParam变量的值 log.info("{}:{}", Thread.currentThread().getName(), localParam.get()); }, "T1").start(); new Thread(() -> { log.info("{}:{}", Thread.currentThread().getName(), localParam.get()); }, "T2").start(); }
... T1:Hello 风尘博客! ... T2:null
打印结果证实,T1
线程中设置的值没法在T2
取出,证实变量ThreadLocal
在各个线程中数据不共享。api
ThreadLocal
的API
ThreadLocal
定义了四个方法:安全
get()
:返回此线程局部变量当前副本中的值;set(T value)
:将线程局部变量当前副本中的值设置为指定值;initialValue()
:返回此线程局部变量当前副本中的初始值;remove()
:移除此线程局部变量当前副本中的值。set()
和initialValue()
区别名称 | set() |
initialValue() |
---|---|---|
定义 | 为这个线程设置一个新值 | 该方法用于设置初始值,而且在调用get() 方法时才会被触发,因此是懒加载。可是若是在get() 以前进行了set() 操做,这样就不会调用 |
区别 | 若是对象生成的时机不禁咱们控制的时候使用 set() 方式 |
对象初始化的时机由咱们控制的时候使用initialValue() 方式 |
ThreadLocal
有一个特别重要的静态内部类ThreadLocalMap
,该类才是实现线程隔离机制的关键。dom
ThreadLocal
实例里面,而是存放在调用线程的threadLocals
变量里面,也就是说:ThreadLocal
类型的本地变量存放在具体的线程内存空间中。ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread
类中有两个ThreadLocalMap
类型的变量,分别是threadLocals
和inheritableThreadLocals
,而ThreadLocalMap
是一个定制化的Hashmap
,专门用来存储线程本地变量。在默认状况下,每一个线程中的这两个变量都为null
,只有当前线程第一次调用ThreadLocal
的set()
或者get()
方法时才会建立它们。ThreadLocal
就是一个工具壳,它经过set()
方法把value
值放入调用线程的threadLocals
里面并存放起来,当调用线程调用它的get()
方法时,再从当前线程的threadLocals
变量里面将其拿出来使用。ide
若是调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals
变量里面,因此当不须要使用本地变量时能够经过调用ThreadLocal
变量的remove()
方法,从当前线程的threadLocals
里面删除该本地变量。工具
另外Thread
里面的threadLocals
被设计为Map
结构是由于每一个线程能够关联多个ThreadLocal
变量。post
Thread
维护着一个ThreadLocalMap
的引用;ThreadLocalMap
是ThreadLocal
的内部类,用Entry
来进行存储;ThreadLocal
的set()
方法时,实际上就是往ThreadLocalMap
设置值,key
是ThreadLocal
对象,值是传递进来的对象;ThreadLocal
的get()
方法时,实际上就是往ThreadLocalMap
获取值,key
是ThreadLocal
对象;ThreadLocal
自己并不存储值,它只是做为一个key
来让线程从ThreadLocalMa
p获取value
。ThreadLocal
的做用因为ThreadLocal
的特性,同一线程在某地方进行设置,在随后的任意地方均可以获取到。从而能够用来保存线程上下文信息。
每一个线程须要一个独享对象(一般是工具类,典型须要使用的类有SimpleDateFormat
和Random
)
这类场景阿里规范里面也提到了:
每一个线程内须要保存全局变量(例如在拦截器中获取用户信息),可让不一样方法直接使用,避免参数传递的麻烦。
演示(完整演示见文末Github)
User.java
@Data public class User { private String userName; public User() { } public User(String userName) { this.userName = userName; } }
UserContextHolder.java
public class UserContextHolder { public static ThreadLocal<User> holder = new ThreadLocal<>(); }
Service1.java
public class Service1 { public void process() { User user = new User("Van"); //将User对象存储到 holder 中 UserContextHolder.holder.set(user); new Service2().process(); } }
Service2.java
public class Service2 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("Service2拿到用户名: " + user.getUserName()); new Service3().process(); } }
Service3.java
public class Service3 { public void process() { User user = UserContextHolder.holder.get(); System.out.println("Service3拿到用户名: " + user.getUserName()); } }
@Test public void threadForParams() { new Service1().process(); }
Service2拿到用户名: Van Service3拿到用户名: Van
ThreadLocal
的好处内存泄露:某个对象不会再被使用,可是该对象的内存却没法被收回
当Thread
运行结束后,ThreadLocal
中的value
会被回收,由于没有任何强引用了。
当Thread
一直在运行始终不结束,强引用就不会被回收,存在如下调用链
Thread-->ThreadLocalMap-->Entry(key为null)-->value
由于调用链中的 value
和 Thread
存在强引用,因此value
没法被回收,就有可能出现OOM
。
如何避免内存泄漏(阿里规范)
调用remove()
方法,就会删除对应的Entry
对象,能够避免内存泄漏,因此使用完ThreadLocal
后,要调用remove()
方法。
ThreadLocal
的空指针问题ThreadLocalNPE.java
public class ThreadLocalNPE { ThreadLocal<Long> longThreadLocal = new ThreadLocal<>(); public void set() { longThreadLocal.set(Thread.currentThread().getId()); } /** * 当前返回值为基本类型,会报空指针异常,若是改为包装类型Long就不会出错 * @return */ public long get() { return longThreadLocal.get(); } }
@Test public void threadLocalNPE() { ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE(); //若是get方法返回值为基本类型,则会报空指针异常,若是是包装类型就不会出错 System.out.println(threadLocalNPE.get()); }
若是get()
方法返回值为基本类型,则会报空指针异常;若是是包装类型就不会出错。这是由于基本类型和包装类型存在装箱和拆箱的关系,因此,咱们必须将get()
方法返回值使用包装类型。