Java中的ThreadLocal详解

写在前面

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。本篇文章将带你详细的剖析一下ThreadLocal,以及在开发中的使用。

代码分析

首先ThreadLocal这个类是位于java.lang这个包中,具体源码可以自行查看。那么ThreadLocal到底是干嘛用的呢?其实它主要就用来在当前线程中存取变量用的,存取变量就存取变量怎么多了一句当前线程中?不要急接着往下看。说到存取变量我们平时可能用到的例如:List,Map,数组等,那么ThreadLocal又是通过什么样方式进行变量的存取呢?答案就是Map(键值对),没错就是Map,而这个Map不是其他的Map而是ThreadLocalMap,ThreadLocalMap是ThreadLocal的静态内部类,在每一个线程Thread中都有一个ThreadLocalMap的成员变量,不信的话看一下Thread类的源码:

public class Thread implements Runnable {
  .......这里省略掉之前的代码

       ThreadLocal.ThreadLocalMap threadLocals = null;

 .......这里省略掉之后的代码
}

这里你会发现在Thread类中确实是有一个ThreadLocalMap的成员变量threadLocals,可以很明确的告诉你ThreadLocal就是在操作这个Thread中的threadLocals,来进行变量的存取,置于为什么ThreadLocal能操作Thread中的threadLocals,以及如何操作的后面通过源码的分析更进一步的了解。截止到目前你需要知道以下几点:

  • 1、 ThreadLocal是在用于存取变量的
  • 2、 ThreadLocal是在当前线程中存取变量的
  • 3、 ThreadLocal是采用Map,也就是键值对的形式进行变量存取的

我们先看看如何使用的,然后在分析分析源码,例如下面就是使用ThreadLocal的一个例子:

public class MyClass {

    //定义了两个ThreadLocal分别为:local1,local2
    private static ThreadLocal<String> local1 = new ThreadLocal<>();

    private static ThreadLocal<Boolean> local2 = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue() {
            return false;
        }
    };

    public static void main(String[] args) {
        System.out.println("local1:" + local1.get());//运行结果:local1:null
        System.out.println("local2:" + local2.get());//运行结果:local2:false
        local1.set("我是local1");
        local2.set(true);
        System.out.println("local1:" + local1.get());//运行结果:local1:我是local1
        System.out.println("local2:" + local2.get());//运行结果:local2:true
    }
}

通过以上代码你会发现我在未进行任何存储操作即未调用set(T t)方法之前:local1返回的是null,local2返回的却是false;这就和local2重写了initialValue方法有关,具体请看ThreadLocal源码分析(这里剔除掉了和我们使用不相干的代码):

package java.lang;//所在包

public class ThreadLocal<T> {
     ...  //省略掉之前的代码
 
    //这个方法看到没是protected的,也就是说希望我们重写的,默认返回null
   //初始化值,在未存储任何值的前提下,去取值返回的结果,默认返回null
    protected T initialValue() {
        return null;
    }

   //从ThreadLocalMap中获取存储的变量
    public T get() {
         //先获取当前的线程对象
        Thread t = Thread.currentThread();
        //再获取线程对象中的ThreadLocalMap成员变量:threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null) {
         //如果不为空,则以自己注意是自己为键,来获取对应的value
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
              //如果value不为空,则直接返回
                return (T)e.value;
        }
        return setInitialValue();
    }

 //往ThreadLocalMap中加入一个变量
    public void set(T value) {
       //先获取当前的线程对象
        Thread t = Thread.currentThread();
      //再获取线程对象中的ThreadLocalMap成员变量:threadLocals
        ThreadLocalMap map = getMap(t);
      //判断返回的ThreadLocalMap是否为空
        if (map != null)
           //如果不为空,则以自己注意是自己为键,value参数为值保寸到线程的ThreadLocalMap中
            map.set(this, value);
        else
          //如果为空,则创建一个ThreadLocalMap,并以自己注意是自己为键,value参数为值,保存到该Map中同时将该Map赋值给当前线程中的ThreadLocalMap成员变量:threadLocals
            createMap(t, value);
    }


 //移除掉ThreadLocalMap存储的变量
 //该方法在jdk1.5加入
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
   
     static class ThreadLocalMap {
         //省略ThreadLocalMap的具体实现
           ......
           ......
     }
}

其实我们在使用ThreadLocal进行变量存取操作的时候用到的也就是以上的几个方法:initialValue(),get(),set(),remove()。类比Map的操作就好,set就是存,get就是取,remove就是删。

这里需要注意的是ThreadLocal进行变量存储的时候都是以自身(即this)为键来进行存储的,所以存储的是set方法而不是put或add,因为一个ThreadLocal对象就是一个键,一个键在Map中只能对应一个值;你要想往当前线程的Map中存几个不同的值,那就需要几个不同的键,即要创建几个ThreadLocal对像,当然不同线程中也可以同时使用同一个ThreadLocal作为键来存取不同的值,他们之间对值的操作互不影响。打个比方:

我用同一个身份证号(同一个ThreadLocal对象),在A商场(线程A)注册了VIP,在B商场(线程B)也注册了VIP,有一天我将A商场的VIP改变为了SVIP,那我在B商场依旧是VIP而不会因为我改变了A商场的身份会影响到我B商场。

总结

ThreadLocal其实还是蛮有用的在Android的UI线程中Handler的Looper就是通过ThreadLocal存储在当前线程中的,所以你只要在UI线程中创建的Handler用的都是之前已经在该线程存储好的Looper。一些开源库其实很多都用到了ThreadLocal,例如何洪辉的EventBus,其他的这里不做过多介绍,有兴趣的可以去看看一些好的开源库的代码,收获会很大。

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

推荐阅读更多精彩内容