ThreadLocal和InheritableThreadLocal深入分析

  通过ThreadLocal和InheritableThreadLocal,我们能够很方便的设计出线程安全的类。JDK底层是如何做到的呢?ThreadLocal和InheritableThreadLocal有什么区别呢与联系呢?为什么有了ThreadLocal类还需要InheritableThreadLocal类,他们与Thread类是什么关系?带着这些问题我们来分析他们的源码。

ThreadLocal

  ThreadLocal是为每一个线程创建一个单独的变量副本,每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。ThreadLocal为多线程环境下变量问题提供了另外一种解决思路。下面来看看ThreadLocal内部是如何做到的。下面分别分析ThreadLocal提供的方法。

  • set方法源码解析
public class ThreadLocal<T> {
 
  /**
**传入一个value参数
**/
    public void set(T value) {
        //首先得到当前线程对象
        Thread t = Thread.currentThread();
        //根据当前线程,得到ThreadLocalMap 引用。
        ThreadLocalMap map = getMap(t);
      //如果map不为空,则把值set到map里
        if (map != null)
            map.set(this, value);
        else
//map为空,则创建一个map对象,并设置值
            createMap(t, value);
    }

//根据Thread得到 ThreadLocalMap 对象
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

//map为空的话,会新建一个ThreadLocalMap,并将引用交给当前Thread的threadLocals 变量
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

}

从上面的源码上我们可以很容易的看出来,** ThreadLocal只是提供方法方便我们进行编码,而真正存变量的地方是在ThreadLocalMap 这个对象上的**。Thread对象持有ThreadLocalMap 的引用,ThreadLocalMap 以ThreadLocal为key。他们之间的关系如下图所示:
ThreadLocal对象关系图
  • get方法源码解析
public class ThreadLocal<T> {
  
    public T get() {
        //得到当前线程
        Thread t = Thread.currentThread();
    //这里取的是Thread里的ThreadLocalMap引用
        ThreadLocalMap map = getMap(t);
        if (map != null) {
//从map里取值,key为ThreadLocal对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
//map为空的话,会调用setInitialValue方法
        return setInitialValue();
    }

//
    private T setInitialValue() {
//得到一个默认的值,initialValue为protected,留给子类实现,
        T value = initialValue();
//得到当前线程
        Thread t = Thread.currentThread();
//再次尝试得到map对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
//调用createMap方法
            createMap(t, value);
        return value;
    }

    protected T initialValue() {
        return null;
    }

}

get方法无非就是得到当前线程的ThreadLocalMap,如果不为空,则根据ThreadLocal为key进行取值,如果为空,则会调用createMap为Thread线程构造一个ThreadLocalMap对象,并返回initialValue方法的值。

InheritableThreadLocal

  InheritableThreadLocal用于子线程能够拿到父线程往ThreadLocal里设置的值。使用代码如下:

public class Test {

    public static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();

    public static void main(String args[]) {
        threadLocal.set(new Integer(456));
        Thread thread = new MyThread();
        thread.start();
        System.out.println("main = " + threadLocal.get());
    }

    static class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("MyThread = " + threadLocal.get());
        }
    }
}

输出结果如下图:
输出结果

如果把上面的InheritableThreadLocal换成ThreadLocal的话,在子线程里的输出将为是空。从上面的代码里也可以看出InheritableThreadLocal是ThreadLocal的子类。下面是InheritableThreadLocal的源码:

package java.lang;
import java.lang.ref.*;

public class InheritableThreadLocal<T> extends ThreadLocal<T> {

 //这个方法留给子类实现
    protected T childValue(T parentValue) {
        return parentValue;
    }

//重写getMap方法,返回的是Thread的inheritableThreadLocals引用
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

//重写createMap方法,构造的ThreadLocalMap会传给Thread的inheritableThreadLocals 变量
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

从源码上看,跟ThreadLocal不一样的无非是ThreadLocalMap的引用不一样了,从逻辑上来讲,这并不能做到子线程得到父线程里的值。那秘密在那里呢?通过跟踪Thread的构造方法,你能够发现是在构造Thread对象的时候对父线程的InheritableThreadLocal进行了复制。下面是Thread的部分源码:

public class Thread implements Runnable {
      //默认人构造方法,会调用init方法进行初使化
      public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

//最终会调用到当前这个方法
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name.toCharArray();
// parent为当前线程,也就是调用了new Thread();方法的线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
//在这里会继承父线程是否为后台线程的属性还有父线程的优先级
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
//这里是重点,当父线程的inheritableThreadLocals 不为空的时候,会调用 ThreadLocal.createInheritedMap方法,传入的是父线程的inheritableThreadLocals。原来复制变量的秘密在这里
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }

}

通过跟踪Thread的构造方法,我们发现只要父线程在构造子线程(调用new Thread())的时候inheritableThreadLocals变量不为空。新生成的子线程会通过ThreadLocal.createInheritedMap方法将父线程inheritableThreadLocals变量有的对象复制到子线程的inheritableThreadLocals变量上。这样就完成了线程间变量的继承与传递。
ThreadLocal.createInheritedMap方法的源码如下:

public class ThreadLocal<T> {
//根据传入的map,构造一个新的ThreadLocalMap
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

   static class ThreadLocalMap {
//这个private的构造方法就是专门给ThreadLocal使用的
          private ThreadLocalMap(ThreadLocalMap parentMap) {
//ThreadLocalMap还是用Entry数组来存储对象的
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];
//这里是复制parentMap数据的逻辑
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    ThreadLocal key = e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }
  }

}

总结:

  • ThreadLocal和InheritableThreadLocal本质上只是为了方便编码给的工具类,具体存数据是ThreadLocalMap 对象。
  • ThreadLocalMap 存的key对象是ThreadLocal,value就是真正需要存的业务对象。
  • Thread里通过两个变量持用ThreadLocalMap 对象,分别为:threadLocals和inheritableThreadLocals。
  • InheritableThreadLocal之所以能够完成线程间变量的传递,是在new Thread()的时候对inheritableThreadLocals对像里的值进行了复制。
  • 子线程通过继承得到的InheritableThreadLocal里的值与父线程里的InheritableThreadLocal的值具有相同的引用,如果父子线程想实现不影响各自的对象,可以重写InheritableThreadLocal的childValue方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容