美文网首页
深入正确理解ThreadLocal类

深入正确理解ThreadLocal类

作者: superxcp | 来源:发表于2018-09-20 19:36 被阅读0次

深入不用说,就是借着源码来深入解析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

相关文章

网友评论

      本文标题:深入正确理解ThreadLocal类

      本文链接:https://www.haomeiwen.com/subject/bornnftx.html