ThreadLocal完全解析

熟悉Android消息机制的话,对ThreadLocal这个类应该都不陌生。Android消息机制中的Looper就是通过ThrealLocal来实现为每个线程建立一个独立Looper的。

简单的Demo

public class ThreadLocalTest {

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        // 给主线程设置ThreadLocal值
        threadLocal.set("I am in main thread");
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                // 给子线程设置ThreadLocal值
                threadLocal.set("I am in sub thread");
                // 在子线程获取ThreadLocal值
                System.out.println(Thread.currentThread() + ":" + threadLocal.get());
            }
        });
        thread.start();
        // 等待子线程执行完
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 在主线程获取ThreadLocal值
        System.out.println(Thread.currentThread() + ":" + threadLocal.get());
    }
    
}

通过上面程序可以发现ThreadLocal在不同的线程设置和获取值是不会相互影响的。

ThreadLocal是如何实现不同线程独立存储的

其实每个线程都一个叫做threadLocalsThreaLocal.ThreadLocalMap类型成员属性。

深入源码可以发现ThreadLocalMap就是一个简易版的Hash表。这个Hash表keyWeakReference<ThreadLocal<?>>valueObject熟悉范性的同学都应该知道ThreadLocal<任意类型>都可以赋值给ThreadLocal<?>类型,相当于实现了范性的多态

ThreadLocal调用set方法的时候,最终都会以当前ThreadLocal的弱引用为key对应的set的值为value存入当前调用线程的threadLocals成员属性中去。

因为如果在不同线程同一ThreadLocal进行set方法调用,在set方法内获取的是不同Thread的threadLocals成员属性,因此达到了同一个ThreadLocal对象的在不同线程中进行值独立存储的要求。

下面是Thread的部分源码:

class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;
...
}

最核心的类---ThreadLocalMap

前面已经说了,这就是一个简易版的在Hash表这里key为ThreadLocal的弱引用因此ThreadLocal是不会导致内存泄漏的。只要某个ThreadLocal的强引用没有了,GC时就ThreadLocalMap对应的ThreadLocal的弱引用也会被回收。

这个过程会产生不新鲜值也就是ThreadLocalMap中某个位置的弱引用的值为null,但其对应的value不为null。

不新鲜值在每次set方法调用的时候一定会进行相应的检测清除,get方法只有在遇到不新鲜值的时候才会进行相应的清除。对应的算法就不进行详解了。

这个简易版的Hash表以16作为初始容量,然后扩容因子2 / 3。以2倍进行扩容。因此其容量始终是2的幂次方。这样的好处和HashMap一样的可以通过与运算轻松的获取存储的值该放在Hash表的哪个位置。然后如果遇到Hash冲突,这里使用的是索引加一法进行冲突的解决。

下面是ThreadLocal.ThreadLocalMap的部分源码:

public class ThreadLocal<T> {
    ...
    static class ThreadLocalMap {
        ...
        private Entry[] table;
        
        static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
      }
    }
        ...
    }
    ...
}

ThreadLocal的实现

public class ThreadLocal<T> {
...
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
  

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
...
}

通过上面部分源码应该很容易发现ThreadLocal就是借助于Thread的threadLocals整个Hash表进行不同线程值的独立存储的。

InheritableThreadLocal---可以继承的ThreadLocal

在Thread的源码中应该可以发现其实Thread有两个ThreadLocal.ThreadLocalMap类。

下面是部分源码:

class Thread implements Runnable {
...
    ThreadLocal.ThreadLocalMap threadLocals = null;

    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    
    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }
    
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
...
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
    }
...

}

threadLocals就是使用普通的ThreadLocal类用到的;inheritableThreadLocals这个成员属性主要就是用于继承创建当前Thread的父Thread中的inheritableThreadLocals。

init方法是继承的具体实现。

下面是InheritableThreadLocal部分源码:

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

    protected T childValue(T parentValue) {
        return parentValue;
    }

    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

InheritableThreadLocal就是ThreadLocal的子类,很简单就是使用线程的threadLocals改成了使用inheritableThreadLocals进行进行存储。

总结

总的来说,ThreadLocal.ThreadLocalMap就是一个通过索引加一法解决冲突的Hash表。

然后每个Thread中有两个ThreadLocal.ThreadLocalMap类型的成员属性:

  • threadLocals用于对ThreadLocal进行存储的。

  • inheritableThreadLocals用于对InheritableThreadLocal进行存储的。其中inheritableThreadLocals会在Thread的init方法调用的时候继承父Thread的inheritableThreadLocals。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 144,100评论 1 305
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 61,765评论 1 257
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 95,393评论 0 213
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 41,312评论 0 183
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 49,108评论 1 260
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 38,923评论 1 178
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 30,527评论 2 274
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,280评论 0 168
  • 想象着我的养父在大火中拼命挣扎,窒息,最后皮肤化为焦炭。我心中就已经是抑制不住地欢快,这就叫做以其人之道,还治其人...
    爱写小说的胖达阅读 29,155评论 6 235
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 32,640评论 0 214
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,391评论 2 216
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 30,744评论 1 231
  • 白月光回国,霸总把我这个替身辞退。还一脸阴沉的警告我。[不要出现在思思面前, 不然我有一百种方法让你生不如死。]我...
    爱写小说的胖达阅读 24,292评论 0 32
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,198评论 2 213
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 31,654评论 3 211
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,655评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,070评论 0 169
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 33,654评论 2 233
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 33,800评论 2 237

推荐阅读更多精彩内容

  • 一、简介 并发编程中,当访问共享数据时,通常需要使用同步技术。但如果数据不发布(逸出)到线程以外,仅仅在单线程中被...
    邱simple阅读 3,339评论 3 12
  • 原理 产生线程安全问题的根源在于多线程之间的数据共享。如果没有数据共享,就没有多线程并发安全问题。ThreadLo...
    Java耕耘者阅读 292评论 0 0
  • 原创文章&经验总结&从校招到A厂一路阳光一路沧桑 详情请戳www.codercc.com 1. ThreadLoc...
    你听___阅读 6,601评论 8 19
  • 这篇文章写的好了http://duanqz.github.io/2018-03-15-Java-ThreadLoc...
    Tancent阅读 312评论 0 2
  • 孩子的周末也是排的满满的,今天早上七点把他叫起来,观看于丹教授主讲的《中小学生人格教育和学习能力》,看了一小部分,...
    卓卓最棒阅读 218评论 0 0