早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。应用这个工具类能够很简洁地编写出柔美的多线程程序。ThreadLocal是指作用域为Thread的局部变量,兴许把它命名为ThreadLocalVariable更容易让人了解一些。此博客很多内容参考了(这篇博客https://www.cnblogs.com/fsmly...).

介绍

多线程拜访同一个共享变量的时候容易呈现并发问题,特地是多个线程对一个变量进行写入的时候,为了保障线程平安,个别使用者在访问共享变量的时候须要进行额定的同步措施能力保障线程安全性。ThreadLocal是除了加锁这种同步形式之外的一种保障一种躲避多线程拜访呈现线程不平安的办法,当咱们在创立一个变量后,如果每个线程对其进行拜访的时候拜访的都是线程本人的变量这样就不会存在线程不平安问题。 ThreadLocal是JDK包提供的,它提供线程本地变量,如果创立一个ThreadLocal变量,那么拜访这个变量的每个线程都会有这个变量的一个正本,在理论多线程操作的时候,操作的是本人本地内存中的变量,从而躲避了线程平安问题,如下图所示:

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的实现原理

从上一节中咱们能够看出,ThreadLocal次要有set和get办法,用于设置和获取线程中的变量,那么ThreadLocal是怎么实现这个性能的呢?和ThreadLocal实现相干的类次要有三个:ThreadLocal、Thread、ThreadLocalMap,三者之间的关系同样如下图所示:

  1. ThreadLocalMap:名字上看是Map,实际上是一个数组,不过它的性能和Map相似,能够依照key查找数据。
  2. Thread:线程大家应该都晓得,那么在ThreadLocal中他起什么作用呢?一个Thread中会蕴含两个ThreadLocalMap,别离用于存储本线程和父线程的ThreadLocal数据。每一个ThreadLocal变量会在线程中对应一条ThreadLocalMap的key-value,其中key是ThreadLocal的惟一Hash值。
  3. ThreadLocal:每个ThreadLocal都会有一个惟一的Hash值,用于查找这个ThreadLocal在ThreadLocalMap中的值;ThreadLocal提供了办法用于获取以后线程的ThreadLocal数据。

数据寄存的地位

ThreadLocal只是一层拜访线程数据的壳,ThreadLocal get和set的数据不会在ThreadLocal的实例中寄存,而是寄存在线程Thread中的ThreadLocalMap,ThreadLocal只是提供了一个拜访这些数据的路径。

ThreadLoca的set办法将value增加到调用线程的ThreadLocalMap中,当调用线程调用get办法时候可能从它的ThreadLocalMap中取出变量。如果调用线程始终不终止,那么这个本地变量将会始终寄存在他的ThreadLocalMap中,所以不应用本地变量的时候须要调用remove办法将ThreadLocalMap中删除不必的本地变量。

set办法存放数据

ThreadLocal办法的set能够向以后线程的ThreadLocalMap中放入数据,存放数据的源码如下所示,Set过程分为以下步骤:

  1. 获取以后线程。
  2. 从以后线程中获取ThreadLocalMap变量。
  3. 如果以后线程的ThreadLocalMap不为空,用以后的ThreadLocal为Key,须要寄存的数据为Value,存放数据。
  4. 如果以后线程的ThreadLocalMap为空,创立ThreadLocalMap并存放数据。
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);}

get办法获取数据

ThreadLocal办法的get能够获取以后线程ThreadLocalMap中寄存的数据,获取存放数据的源码如下所示,get过程分为以下步骤:

  1. 获取以后线程
  2. 从以后线程中获取ThreadLocalMap变量。
  3. 如果ThreadLocalMap变量不为null,就能够在map中查找到本地变量的值。
  4. 如果ThreadLocalMap变量为null,那么就初始化以后线程的ThreadLocalMap。
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不反对继承性

同一个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());    }}

InheritableThreadLocal类

在下面说到的ThreadLocal类是不能提供子线程拜访父线程的本地变量的,而InheritableThreadLocal类则能够做到这个性能,上面是该类的源码,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个办法。咱们接下来别离介绍一下三种办法的用途。

  1. createMap:当线程中不存在ThreadLocalMap变量,然而调用set或者get办法设置值的时候,须要初始化ThreadLocalMap变量时调用该办法。
  2. getMap:须要获取线程的ThreadLocalMap时调用该办法,这里返回的ThreadLocalMap始终为InheritableThreadLocalMap。
  3. childValue:在创立新线程的时候,如果父线程有ThreadLocalMap变量并且容许inherite ThreadLocalMap,那么程序会复制父线程的InheritableThreadLocal到子线程中,childValue示意在复制过程中如何依据父线程中失去数据生成线程中的数据。

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内存透露

通过后面的剖析咱们晓得,ThreadLocal的线程数据是寄存在ThreadLocalMap中的,所以如果ThreadLocal呈现内存透露,那么必定是ThreadLocalMap中存储的数据呈现了泄露,咱们须要看看ThreadLocalMap中的数据结构。ThreadLocalMap的数据结构如下所示,ThreadLocalMap中的数据存储在一个Entry数组中,Entry中有对ThreadLocal的WeakReference。

什么状况下会呈现内存泄露呢?

  1. 当一个线程调用ThreadLocal的set办法设置变量的时候,以后线程的ThreadLocalMap就会寄存一个记录,这个记录的key值为ThreadLocal的弱援用,value就是通过set设置的值。
  2. 如果以后线程始终存在且没有调用该ThreadLocal的remove办法,如果这个时候别的中央还有对ThreadLocal的援用,那么以后线程中的ThreadLocalMap中会存在对ThreadLocal变量的援用和value对象的援用,是不会开释的,就会造成内存透露。
  3. 思考这个ThreadLocal变量没有其余强依赖,如果以后线程还存在,因为线程的ThreadLocalMap外面的key是弱援用,所以以后线程的ThreadLocalMap外面的ThreadLocal变量的弱援用在gc的时候就被回收,然而对应的value还是存在的这就可能造成内存透露(因为这个时候ThreadLocalMap会存在key为null然而value不为null的entry项)。

总结:THreadLocalMap中的Entry的key应用的是ThreadLocal对象的弱援用,在没有其余中央对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,然而对应的不会被回收,这个时候Map中就可能存在key为null然而value不为null的项,这须要理论的时候应用结束及时调用remove办法防止内存透露。

Java中的四种援用类型

上文中咱们说到了WeakReference,大家可能对这个词有点生疏,Java中有四种援用类型:

  1. 强援用:Java中默认的援用类型,一个对象如果具备强援用那么只有这种援用还存在就不会被GC。
  2. 软援用:简言之,如果一个对象具备弱援用,在JVM产生OOM之前(即内存短缺够应用),是不会GC这个对象的;只有到JVM内存不足的时候才会GC掉这个对象。软援用和一个援用队列联结应用,如果软援用所援用的对象被回收之后,该援用就会退出到与之关联的援用队列中。
  3. 弱援用(这里探讨ThreadLocalMap中的Entry类的重点):如果一个对象只具备弱援用,那么这个对象就会被垃圾回收器GC掉(被弱援用所援用的对象只能生存到下一次GC之前,当产生GC时候,无论以后内存是否足够,弱援用所援用的对象都会被回收掉)。弱援用也是和一个援用队列联结应用,如果弱援用的对象被垃圾回收期回收掉,JVM会将这个援用退出到与之关联的援用队列中。若援用的对象能够通过弱援用的get办法失去,当援用的对象呗回收掉之后,再调用get办法就会返回null;
  4. 虚援用:虚援用是所有援用中最弱的一种援用,其存在就是为了将关联虚援用的对象在被GC掉之后收到一个告诉。(不能通过get办法取得其指向的对象)。

我是御狐神,欢送大家关注我的微信公众号:wzm2zsd

本文最先公布至微信公众号,版权所有,禁止转载!