深入正确理解ThreadLocal类

深入不用说,就是借着源码来深入解析ThreadLocal类,正确意思是以前或者说网上有很多文章传递了一种错误的理解,导致我之前都对ThreadLocal类的理解都是反的,现在趁着写分享的机会,修正下这个错误的理解。

本文目录如下:
一. 深入解析ThreadLocal类
二. ThreadLocal的机制的优点
三. ThreadLocal的应用场景
四. 参考资料

一. 深入解析ThreadLocal类

ThreadLocal,很多人知道的这个类的作用是在该类内部为每个线程创建了一个数据副本,当每个线程想获取自己数据时,会调用get()方法取得。

先说一下我之前的错误理解,(注意是错误理解,也有些文章说是JDK1.3以前的实现机制):创建一个静态的Map,将当前线程thread作为key,为当前线程创建的数据变量作为value,put到该Map中。每个线程取数据时,根据自己的线程号直接调用Map的get()方法就取到了。

However,ThreadLocal的实现与这种方法刚好相反:在每个线程类中 有一个Map,该Map不是传统的Map,是ThreadLocalMap,这个map是ThreadLocal的一个静态内部类,不在ThreadLocal中创建,只在Thread线程类中创建,这个map中下面是多个Entry键值对,Entry的key是该ThreadLocal实例,value则是线程的数据变量。说到这里对于一些没看过这个类的人来说可能还是不理解,下面就结合着源码详细的解读一下。

首先,我们先来看下ThreadLocal类里面提供的几个方法:

public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

get():获取ThreadLocal在当前线程中保存的变量副本;
set():设置当前线程中变量的副本;
remove():移除当前线程中变量的副本;
initialValue():在使用ThreadLocal时重写,可以使未set()数据之前,当前线程第一次get()时获得一个预置的数据(一般获得一个不为null的默认数据);

先看一下get()方法:

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();
}

第一句是获取当前线程,然后通过getMap()方法获得一个ThreadLocalMap,如果不为空,获取到这个map中的Entry键值对对象,注意,这时候获取键值对传进去的是this(也就是ThreadLocal实例),而不是当前线程(这就说明线程内部set()键值时,key是当前ThreadLocal实例),如果获取成功,返回value值。
如果map为空,通过setInitialValue()方法返回一个初始化的value值,setInitialValue()方法在后边会讲到。

现在我们来看下调用getMap(t)返回ThreadLocalMap时,这个方法里面做了什么:

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

看了吧,在getMap中,这个ThreadLocalMap竟然是线程自己返回的!这就意味着,ThreadLocalMap是线程Thread类自己的内部变量啊,与在自己的类中new一个其它普通的实例感觉差不多!那这个threadLocals字段想必大家都猜到了,在Thread类内部就是一个ThreadLocalMap对象:

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

然后,我们分析下ThreadLocalMap这个类里面 是什么样子:

static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

可以看到,ThreadLocalMap的Entry继承了WeakReference弱引用,并且以ThreadLocal实例作为键值。另外,弱引用在下次GC回收时会被回收掉的,如果留下一个以null作为键值的Value可能会出现内存泄露的,这个问题在这里不多说,感兴趣的同志可以自己了解一下。

然后,还记得当时getMap()为空的情况吗?如果没有set()的话会调用setInitialValue()返回个默认值:

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}

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

如源码所示,如果map不为空,以当前ThreadLocal为键,初始化的Value为值,set()到该线程的ThreadLocalMap中去(至于为什么再判断一遍map是否为空,可能考虑了线程安全和方法复用的场景);如果map为空,就新创建一个。

因此,经过这些分析,大部分朋友应该明白了ThreadLocal是如何为每个线程创建变量副本的,其实我理解的只是通过ThreadLocal建立了一个公共访问数据的类,创建实例等操作还是由每个线程自己完成。

二. ThreadLocal的机制的优点

  1. 每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了;
  2. 把map放到各自线程中带来的好处是 因为各线程访问的map是各自不同的map,所以不需要同步,速度会快些;而如果把所有线程要用的对象都放到一个静态map中的话,多线程并发访问需要进行同步;

三. ThreadLocal的应用场景

举两个例子,通过这两个例子能看出ThreadLocal的典型应用场景;

  • 第一个应用:hibernate中典型的ThreadLocal的应用
private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
}  

首先判断当前线程中有没有session,没有的话,通过sessionFactory().openSession()来创建一个,再把session set到当前线程中,实际是放到了当前线程的ThreadLocalMap中,这样,对于这个session的唯一引用就是当前线程中的ThreadLocalMap,而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。 显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。

试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行。

  • 第二个应用:证明通过ThreadLocal能达到在每个线程中创建变量副本的效果
public class Test {
    ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
    ThreadLocal<String> stringLocal = new ThreadLocal<String>();
 
     
    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }
     
    public long getLong() {
        return longLocal.get();
    }
     
    public String getString() {
        return stringLocal.get();
    }
     
    public static void main(String[] args) throws InterruptedException {
        final Test test = new Test();
         
         
        test.set();
        System.out.println(test.getLong());
        System.out.println(test.getString());
     
         
        Thread thread1 = new Thread(){
            public void run() {
                test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();
         
        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

四.参考资料

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

推荐阅读更多精彩内容