ThreadLocal

ThreadLocal是一个线程内部数据存储的工具类。

在每一个线程中都有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,用于存放自己线程的一些数据,其它线程不能对此变量进行访问。对于同一个static ThreadLocal,不同线程只能从getsetremove方法来获取自己的变量值,这样的操作并不影响其他线程。主要有以下几个方法:

  • ThreadLocal.get():获取ThreadLocal中当前线程副本变量的值。
  • ThreadLocal.set():设置ThreadLocal中当前线程副本变量的值。
  • ThreadLocal.remove():移除ThreadLocal中当前线程副本变量的值。
  • ThreadLocal.initialValue()ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
    看下面这个例子:
public class ThreadLocalClass {
    private static ThreadLocal mThreadLocal = new ThreadLocal(){
        /**
         * mThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
         * @return
         */
        @Override
        protected Object initialValue() {
            System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
            return super.initialValue();
        }
    };

    public static void main(String[] args) {
        final ThreadLocalClass mThreadLocalClass = new ThreadLocalClass();

        Thread threadA = new Thread(new MyTaskA("MyTaskA"));
        threadA.start();
        try {
            threadA.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程threadA执行完成后-->value:"+ mThreadLocal.get());

        Thread threadB = new Thread(new MyTaskB("MyTaskB"));
        threadB.start();
        try {
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("线程threadB执行完成后-->value:"+ mThreadLocal.get());
    }

    private static class MyTaskA implements Runnable{
        String name;
        public MyTaskA(String name){
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("MyTaskA线程执行前-->"+ name+":"+ mThreadLocal.get());
            for (int i = 0; i < 5; i++) {
                if (null == mThreadLocal.get()){
                    mThreadLocal.set(0);
                    System.out.println("线程"+ name+":"+ mThreadLocal.get());
                }else{
                    int getValue = (int) mThreadLocal.get();
                    mThreadLocal.set(getValue+1);
                    System.out.println("线程"+ name+","+ mThreadLocal.get());
                    if (i == 2){
                        mThreadLocal.remove();
                    }
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("MyTaskA线程-->"+ name+":"+ mThreadLocal.get());
        }
    }

    private static class MyTaskB implements Runnable{
        String name;
        public MyTaskB(String name){
            this.name = name;
        }

        @Override
        public void run() {
            System.out.println("MyTaskB线程执行前-->"+ name+":"+ mThreadLocal.get());
            for (int i = 0; i < 5; i++) {
                if (null == mThreadLocal.get()){
                    mThreadLocal.set("A"+i);
                    System.out.println("线程"+ name+":"+mThreadLocal.get());
                }else{
                    String getValue = (String) mThreadLocal.get();//获取共享变量
                    mThreadLocal.set(getValue+"B");//设置共享变量值
                    System.out.println("线程"+ name+","+ mThreadLocal.get());
                    if (i == 2){
                        mThreadLocal.remove();//清除共享变量
                    }
                }

                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("MyTaskB线程-->"+ name+":"+ mThreadLocal.get());
        }
    }

}
 
 

运行结果为:

调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
MyTaskA线程执行前-->MyTaskA:null
线程MyTaskA:0
线程MyTaskA,1
线程MyTaskA,2
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程MyTaskA:0
线程MyTaskA,1
MyTaskA线程-->MyTaskA:1
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程threadA执行完成后-->value:null
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
MyTaskB线程执行前-->MyTaskB:null
线程MyTaskB:A0
线程MyTaskB,A0B
线程MyTaskB,A0BB
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程MyTaskB:A3
线程MyTaskB,A3B
MyTaskB线程-->MyTaskB:A3B
线程threadB执行完成后-->value:null

这样就看出来ThreadLocal值之间有没有相互影响
在一个线程中,这样的共享变量值可以有多个。看下面的例子:

public class ThreadLocalClass {
    private ThreadLocal mLongThreadLocal = new ThreadLocal(){
        /**
         * mLongThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
         * @return
         */
        @Override
        protected Long initialValue() {
            return 0L;
        }
    };
    private ThreadLocal mStringThreadLocal = new ThreadLocal(){
        /**
         * mStringThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
         * @return
         */
        @Override
        protected String initialValue() {
            return "initValue";
        }
    };

    public static void main(String[] args) {
        final ThreadLocalClass mThreadLocalClass = new ThreadLocalClass();
         mThreadLocalClass.set();
        System.out.println("Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());

        Thread thread1 = new Thread(new MyTaskC(mThreadLocalClass, "MyTaskC"));
        thread1.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread:CurrThread:"+ Thread.currentThread().getName());
                System.out.println("Thread:Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
                mThreadLocalClass.set();
                System.out.println("Thread:Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
            }
        });
        thread2.start();
        try {
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
    }

    private void set(){
        mLongThreadLocal.set(Thread.currentThread().getId());
        mStringThreadLocal.set(Thread.currentThread().getName());
    }

    private Long getLong(){
        return mLongThreadLocal.get();
    }

    private String getString(){
        return mStringThreadLocal.get();
    }

    private static class MyTaskC implements Runnable{
        String name;
        ThreadLocalClass threadLocal;
        public MyTaskC(ThreadLocalClass threadLocal, String name){
            this.name = name;
            this.threadLocal = threadLocal;
        }

        @Override
        public void run() {
            System.out.println("MyTaskC-->CurrThread:"+ Thread.currentThread().getName());
            System.out.println("MyTaskC:Long:"+ threadLocal.getLong()+", String:"+ threadLocal.getString());
            threadLocal.set();
            System.out.println("MyTaskC:Long:"+ threadLocal.getLong()+", String:"+ threadLocal.getString());
        }
    }
}

运行结果为:

Long:1, String:main//主线程中输出
MyTaskC-->CurrThread:Thread-0//子线程名称
MyTaskC:Long:0, String:initValue//调用get方法设置初值
MyTaskC:Long:11, String:Thread-0//子线程输出
Long:1, String:main//子线程执行完成后,在主线程中输出
Thread:CurrThread:Thread-1//子线程
Thread:Long:0, String:initValue//调用get方法设置初值
Thread:Long:12, String:Thread-1//子线程输出
Long:1, String:main//子线程执行完成后,在主线程中输出

上面这个例子在线程中生成了两个fuben变量mLongThreadLocalmStringThreadLocal,这两个值在主线程和两个子线程中的输出互不影响。

总结一下:
  • 通过ThreadLocal创建的变量,都存储在每个线程自己的参数threadLocals中。
  • 通过ThreadLocal创建的变量,可以有多个。
  • 通过ThreadLocal创建的变量,必须先set方法后在调用get方法,除非重写initialValue方法。因为先调用get方法会报空指针异常,这个异常来源于初始化的默认值为null

我们来看看这个类的源码实现

  • get源码实现
1    public T get() {
2        Thread var1 = Thread.currentThread();
3        ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
4        if(var2 != null) {
5            ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
6            if(var3 != null) {
7                Object var4 = var3.value;
8                return var4;
9            }
10        }
11        return this.setInitialValue();
    }

第二行代码是获取当前线程,第三行是通过方法this.getMap(var1)返回ThreadLocal.ThreadLocalMap类型的map值,第四到十行就是根据这个map值不为空时,取出对应的值,第11行如果map为空则调用方法setInitialValue返回值。

    ThreadLocal.ThreadLocalMap getMap(Thread var1) {
        return var1.threadLocals;
    }

从上面代码看,getMap方法就是返回了当前线程的threadLocals参数值。进入这个参数:

    ThreadLocalMap threadLocals = null;

可见它是个ThreadLocalMap类型的值,它是ThreadLocal类的内部类。我们在来看ThreadLocalMap的实现:

    static class ThreadLocalMap {
        ......
        ThreadLocalMap(ThreadLocal var1, Object var2) {
            this.size = 0;
            this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
            int var3 = var1.threadLocalHashCode & 15;
            this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
            this.size = 1;
            this.setThreshold(16);
        }
        ......
        static class Entry extends WeakReference> {
            Object value;

            Entry(ThreadLocal var1, Object var2) {
                super(var1);
                this.value = var2;
            }
        }
        ......
    }

它的构造方法中第一个参数以ThreadLocal为参数,在ThreadLocalMap的内部类Entry中,继承与WeakReference并以ThreadLocal为键。这里只有key为若引用,而value为强引用;而key使用若引用后,生命周期只能到下次GC之前。

这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露
那么怎么解决这个问题呢?就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
 1   private T setInitialValue() {
 2       Object var1 = this.initialValue();
 3       Thread var2 = Thread.currentThread();
 4       ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
 5       if(var3 != null) {
 6           var3.set(this, var1);
 7       } else {
 8           this.createMap(var2, var1);
 9       }
 10        return var1;
    }

第2行直接调用初始化方法initialValue得到一个Object对象值,第3行获取当前线程,第4行得到ThreadLocal.ThreadLocalMap类型的map值,如果此值不为空,则设置键值对,为空,则新建。
初始化initialValue方法代码为:

    protected T initialValue() {
        return null;
    }

默认情况下,初值为null。新建方法createMap代码为:

    void createMap(Thread var1, T var2) {
        var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
    }

这样就生成了ThreadLocal.ThreadLocalMap对象。

  • set源码实现:
    public void set(T var1) {
        Thread var2 = Thread.currentThread();
        ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
        if(var3 != null) {
            var3.set(this, var1);
        } else {
            this.createMap(var2, var1);
        }
    }
  • remove源码实现:
    public void remove() {
        ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
        if(var1 != null) {
            var1.remove(this);
        }
    }
从上面的源码分析来看,在Thread中有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它就是用来存储真正的ThreadLocal副本变量的,以当前ThreadLocal为键,以T类型的变量为value
起初时,Thread中的成员变量threadLocals值为空,通过ThreadLocal共享变量调用get或者set方法后,变量threadLocals开始被初始化,并且以当前的ThreadLocal为键,以要保存的值为value保存到threadLocals之中。然后在当前线程中,如果要使用ThreadLocal共享变量就可以使用get,set或者remove方法来进行操作了。
归纳一下也就是:
  • 每个线程中都有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals
  • threadLocals里面存储的是线程的本地对象(key)和线程的变量副本(value)
  • 每个线程中的threadLocals变量,都是由工具类ThreadLocal来进行维护的。可以设置和获取副本变量的值。
参考:
  • https://www.cnblogs.com/dolphin0520/p/3920407.html

你可能感兴趣的:(ThreadLocal)