美文网首页
ThreadLocal

ThreadLocal

作者: 小和尚恋红尘 | 来源:发表于2018-08-30 17:31 被阅读0次
    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<Object> mThreadLocal = new ThreadLocal<Object>(){
            /**
             * 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<Long> mLongThreadLocal = new ThreadLocal<Long>(){
            /**
             * mLongThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
             * @return
             */
            @Override
            protected Long initialValue() {
                return 0L;
            }
        };
        private ThreadLocal<String> mStringThreadLocal = new ThreadLocal<String>(){
            /**
             * 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<ThreadLocal<?>> {
                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来进行维护的。可以设置和获取副本变量的值。
    参考:

    相关文章

      网友评论

          本文标题:ThreadLocal

          本文链接:https://www.haomeiwen.com/subject/psirwftx.html