InheritableThreadLocal变量无法传递

背景知识

我们都知道InheritableThreadLocal类型的变量可以在父子线程之间传递。具体实现为:每个线程都维护了threadLocalsinheritableThreadLocals成员变量,线程在初始化时会根据父线程的inheritableThreadLocals创建自身的inheritableThreadLocals。具体的创建由ThreadLocal类负责实现。

线程的threadLocalsinheritableThreadLocals成员变量都是ThreadLocal.ThreadLocalMap类型,这个类在功能上类似于HashMap,是ThreadLocal或者InheritableThreadLocal类型变量的集合。
Thread.java

/* ThreadLocal values pertaining to this thread. This map is maintained
 * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal values pertaining to this thread. This map is
 * maintained by the InheritableThreadLocal class.
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

init是线程的初始化方法,注意这里省略了无关的一些代码
Thread.java

/**
 * Initializes a Thread.
 *
 * @param g the Thread group
 * @param target the object whose run() method gets called
 * @param name the name of the new Thread
 * @param stackSize the desired stack size for the new thread, or
 *        zero to indicate that this parameter is to be ignored.
 * @param acc the AccessControlContext to inherit, or
 *            AccessController.getContext() if null
 * @param inheritThreadLocals if {@code true}, inherit initial values for
 *            inheritable thread-locals from the constructing thread
 */
private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ……
    Thread parent = currentThread();
    ……
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
            ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ……
}

问题描述

笔者最近在使用InheritableThreadLocal时无意中碰到了一个问题。先来看我自定义的ThreadLocal类型,这个类的默认初始值为当前时间戳。

MyThreadLocal.java

import java.util.Date;

public class MyThreadLocal extends InheritableThreadLocal<Long> {

    @Override
    protected Long initialValue() {
        return new Date().getTime();
    }
}

在这里我创建了一个子线程myThread,然后打印当前线程的ThreadLocal变量的值,过2秒后启动子线程,由子线程打印这个ThreadLocal变量的值。
MyThread.java

public class MyThread extends Thread {

    public static ThreadLocal<Long> tl = new MyThreadLocal();

    @Override
    public void run() {
        System.out.println(tl.get());
    }

    public static void main(String args[]) {
        MyThread myThread = new MyThread();
        System.out.println(tl.get());

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        myThread.start();
    }
}

由于MyThreadLocal继承了InheritableThreadLocal,因此我期望两者可以打印出相同的结果。然而无论运行多少遍这个程序,实际结果总是相差2秒以上(非常接近2秒)。由此可见这个ThreadLocal变量并没有从父线程传递到子线程。

问题分析

笔者试着通过分析源代码来查找原因。可以看到线程中的threadLocals变量默认为null,而且Thread类中并没有任何对其赋值的地方,事实上通过注释也可以发现threadLocalsinheritableThreadLocals都是由ThreadLocal类维护的

ThreadLocal类的createMap方法为线程初始化threadLocals变量
ThreadLocal.java

/**
 * Create the map associated with a ThreadLocal. Overridden in
 * InheritableThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the map
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

InheritableThreadLocal类的createMap方法为线程初始化inheritableThreadLocals变量
InheritableThreadLocal.java

/**
 * Create the map associated with a ThreadLocal.
 *
 * @param t the current thread
 * @param firstValue value for the initial entry of the table.
 */
void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

而调用createMap方法的地方为ThreadLocal的set方法和setInitialValue方法,后者被get方法调用。ThreadLocal本身的构造函数并没有做任何事情,即使我们通过重写initialValue方法设置默认值。也就是说,如果我们创建了ThreadLocal类型的对象后没有调用set方法设置初始值或者调用get方法设置默认的初始值,那么这个对象不会被添加到线程的threadLocals或者inheritableThreadLocals中。此时创建的子线程也不能获取到父线程的InheritableThreadLocal类型的变量。由此导致了InheritableThreadLocal变量无法在父子线程之间传递。
当然解决方法也很简单,在子线程创建之前调用set方法设置变量的值或者调用get方法获取默认值即可。

结论

InheritableThreadLocal类型的变量要在子线程创建之前设置初始值,否则不能在父子线程之间共享。

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

推荐阅读更多精彩内容

  • 本篇文章的主要内容如下: 1、Java中的ThreadLocal2、 Android中的ThreadLocal3、...
    Sophia_dd35阅读 617评论 0 5
  • Java SE 基础: 封装、继承、多态 封装: 概念:就是把对象的属性和操作(或服务)结合为一个独立的整体,并尽...
    Jayden_Cao阅读 2,105评论 0 8
  • (一)Java部分 1、列举出JAVA中6个比较常用的包【天威诚信面试题】 【参考答案】 java.lang;ja...
    独云阅读 7,092评论 0 62
  • 人踩着落叶回家,觉着这样会过一辈子。 每天一百字||今天继续说胸胁痛,顺便再发个广告。如果气滞胸胁就会胸胁痛,在肠...
    张安默生阅读 185评论 0 0
  • 我关闭了微信朋友圈的发现功能。今天开始,我大概也不会再更新自己的朋友圈。若不是因为工作,仍需要用到微信,我想连微信...
    俗然阅读 954评论 4 26