ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存。
简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个ThreadLocal.ThreadLocalMap,把数据进行隔离,每个线程的数据不共享,自然就没有线程安全方面的问题了.
ThreadLocal可以实现每个线程绑定自己的值,即每个线程有各自独立的副本而互相不受影响。一共有四个方法:get, set, remove, initialValue。可以重写initialValue()方法来为ThreadLocal赋初值
本例开启了两个线程,在每个线程内部都设置了本地变量的值,代码如下:
public class ThreadLocalDemo { //创建ThreadLocal变量 static ThreadLocal<String> localParam = new ThreadLocal<>(); public static void main(String[] args) { //创建2个线程,分别设置不同的值 new Thread(()->{ localParam.set("hello java"); //打印当前线程本地内存中的localParam变量的值 System.out.println(Thread.currentThread().getName() + ":" + localParam.get()); },"T1").start(); new Thread(()->{ localParam.set("hello web"); System.out.println(Thread.currentThread().getName() + ":" + localParam.get()); },"T2").start(); } } 复制代码
结果:
T1:hello java T2:hello web 复制代码
说明每个线程都会有这个变量的本地副本,当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量。
ThreadLocal.ThreadLocalMap threadLocals = null; ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; 复制代码
其实 每个线程的本地变量不是存放在ThreadLocal实例里面,而是存放在调用线程的threadLocals变量里面 。也就是说, ThreadLocal类型的本地变量存放在具体的线程内存空间中。
Thread类中有两个ThreadLocalMap类型的变量,分别是threadLocals和inheritableThreadLocals,而ThreadLocalMap是一个定制化的Hashmap,专门用来存储线程本地变量。在默认情况下,每个线程中的这两个变量都为null,只有当前线程第一次调用ThreadLocal的set或者get方法时才会创建它们。
ThreadLocal就是一个工具壳, 它通过set方法把value值放入调用线程的threadLocals里面并存放起来,当调用线程调用它的get方法时,再从当前线程的threadLocals变量里面将其拿出来使用 。
如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。
另外Thread里面的threadLocals被设计为map结构是因为每个线程可以关联多个ThreadLocal变量。
public void set(T value) { //获取当前线程 Thread t = Thread.currentThread(); //将当前线程作为key,去查找对应的线程变量,找到则设置 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else //第一次调用就创建当前线程对应的ThreadLocalMap createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 复制代码
如果getMap(t)的返回值不为空,则把value值设置到threadLocals中,也就是把当前变量值放入当前线程的内存变量threadLocals中。threadLocals是一个HashMap结构,其中key就是当前ThreadLocal的实例对象引用,value是通过set方法传递的值 。
如果getMap(t)返回空值则说明是第一次调用set方法,这时创建当前线程的threadLocals变量。
public T get() { //获取当前线程 Thread t = Thread.currentThread(); //获取当前线程的threadLocals变量 ThreadLocalMap map = getMap(t); //如果threadLocals不为null,则返回对应本地变量的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } //threadLocals为空则初始化当前线程的threadLocals成员变量 return setInitialValue(); } private T setInitialValue() { //初始化为Null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } protected T initialValue() { return null; } 复制代码
如果ThreadLocal没有调用set方法,直接调用get方法,则会返回null.
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } 复制代码
如果当前线程的threadLocals变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量。
ThreadLocal归纳下来就2类用途:
由于ThreadLocal的特性,同一线程在某地方进行设置,在随后的任意地方都可以获取到。从而可以用来保存线程上下文信息。
常用的比如每个请求怎么把一串后续关联起来,就可以用ThreadLocal进行set,在后续的任意需要记录日志的方法里面进行get获取到请求id,从而把整个请求串起来。
还有比如Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。
局限性:每个线程往ThreadLocal中读写数据是线程隔离,互相之间不会影响的,但是 ThreadLocal无法解决共享对象的更新 问题!
把ThreadLocal定义为static还有一个好处就是,由于ThreadLocal有强引用在,那么在ThreadLocalMap里对应的Entry的键会永远存在,那么执行remove的时候就可以正确进行定位到并且删除!
最佳实践做法应该为: try { // 其它业务逻辑 } finally { threadLocal对象.remove(); } 复制代码
注意:在线程池的情况下,在ThreadLocal业务周期处理完成时,最好显式的调用remove()方法,清空”线程局部变量”中的值。正常情况下使用ThreadLocal不会造成内存溢出,弱引用的只是threadLocal,保存的值依然是强引用的,如果threadLocal依然被其他对象强引用,”线程局部变量”是无法回收的。
在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为HashMap,其中key为我们定义的ThreadLocal变量的this引用,value则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。