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<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来进行维护的。可以设置和获取副本变量的值。
参考:
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,835评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,900评论 2 383
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,481评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,303评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,375评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,729评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,877评论 3 404
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,633评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,088评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,443评论 2 326
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,563评论 1 339
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,251评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,827评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,712评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,943评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,240评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,435评论 2 348

推荐阅读更多精彩内容