早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。ThreadLocal是指作用域为Thread的局部变量,也许把它命名为ThreadLocalVariable更容易让人理解一些。此博客很多内容参考了(这篇博客https://www.cnblogs.com/fsmly/p/11020641.html).
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。 ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示:
下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示:
package test; public class ThreadLocalTest { static ThreadLocal<String> localVar = new ThreadLocal<>(); static void print(String str) { //打印当前线程中本地内存中本地变量的值 System.out.println(str + " :" + localVar.get()); //清除本地内存中的本地变量 localVar.remove(); } public static void main(String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run() { //设置线程1中本地变量的值 localVar.set("localVar1"); //调用打印方法 print("thread1"); //打印本地变量 System.out.println("after remove : " + localVar.get()); } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { //设置线程1中本地变量的值 localVar.set("localVar2"); //调用打印方法 print("thread2"); //打印本地变量 System.out.println("after remove : " + localVar.get()); } }); t1.start(); t2.start(); } }
从上一节中我们可以看出,ThreadLocal主要有set和get方法,用于设置和获取线程中的变量,那么ThreadLocal是怎么实现这个功能的呢?和ThreadLocal实现相关的类主要有三个:ThreadLocal、Thread、ThreadLocalMap,三者之间的关系同样如下图所示:
ThreadLocal只是一层访问线程数据的壳,ThreadLocal get和set的数据不会在ThreadLocal的实例中存放,而是存放在线程Thread中的ThreadLocalMap,ThreadLocal只是提供了一个访问这些数据的途径。
ThreadLoca的set方法将value添加到调用线程的ThreadLocalMap中,当调用线程调用get方法时候能够从它的ThreadLocalMap中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的ThreadLocalMap中,所以不使用本地变量的时候需要调用remove方法将ThreadLocalMap中删除不用的本地变量。
ThreadLocal方法的set可以向当前线程的ThreadLocalMap中放入数据,存放数据的源码如下所示,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); }
ThreadLocal方法的get可以获取当前线程ThreadLocalMap中存放的数据,获取存放数据的源码如下所示,get过程分为以下步骤:
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; }
同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)。
package test; public class ThreadLocalTest2 { //(1)创建ThreadLocal变量 public static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { //在main线程中添加main线程的本地变量 threadLocal.set("mainVal"); //新创建一个子线程 Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("子线程中的本地变量值:"+threadLocal.get()); } }); thread.start(); //输出main线程中的本地变量值 System.out.println("mainx线程中的本地变量值:"+threadLocal.get()); } }
在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能,下面是该类的源码,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。我们接下来分别介绍一下三种方法的用处。
public class InheritableThreadLocal<T> extends ThreadLocal<T> { /** * Creates an inheritable thread local variable. */ public InheritableThreadLocal() {} /** * Computes the child's initial value for this inheritable thread-local * variable as a function of the parent's value at the time the child * thread is created. This method is called from within the parent * thread before the child is started. * <p> * This method merely returns its input argument, and should be overridden * if a different behavior is desired. */ protected T childValue(T parentValue) { return parentValue; } /** * Get the map associated with a ThreadLocal. */ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; } /** * Create the map associated with a ThreadLocal. */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); } }
总结:Thread会在构造函数中将父线程的inheritableThreadLocals成员变量的值赋值到新的ThreadLocalMap对象中。返回之后赋值给子线程的inheritableThreadLocals。InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。
通过前面的分析我们知道,ThreadLocal的线程数据是存放在ThreadLocalMap中的,所以如果ThreadLocal出现内存泄漏,那么肯定是ThreadLocalMap中存储的数据出现了泄露,我们需要看看ThreadLocalMap中的数据结构。ThreadLocalMap的数据结构如下所示,ThreadLocalMap中的数据存储在一个Entry数组中,Entry中有对ThreadLocal的WeakReference。
什么情况下会出现内存泄露呢?
总结:THreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。
上文中我们说到了WeakReference,大家可能对这个词有点陌生,Java中有四种引用类型:
我是御狐神,欢迎大家关注我的微信公众号:wzm2zsd
本文最先发布至微信公众号,版权所有,禁止转载!