ThreadLocal原理详细解析

1.ThreadLocal概念

ThreadLocal,可以叫做线程本地变量或线程本地存储,顾名思义就是ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。其实就是通过空间换时间的方式来取得对每个线程各自变量的共享。

变量值的共享可以使用 public static 变量的形式,所有的线程都使用同一个被 public static 修饰的变量。ThreadLocal主要解决的就是每个线程绑定自己的值,可以将ThreadLocal类比喻成全 局存放数据的盒子,盒子中可以存储每个线程的私有变量。

2.具体源码分析

  • ThreadLocal有以下方法:

  •  方法的具体实现:
    • 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();
          }

      1.获取当前线程t;2.通过getMap方法获取一个ThreadLocalMap类型的map;3.获取这个键值对,注意这里获取键值对传进去的是  this,而不是当前线程t。

      如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。

      • getMap方法:

        ThreadLocalMap getMap(Thread t) {
                return t.threadLocals;
            }
        
        //Thread类
        ThreadLocal.ThreadLocalMap threadLocals = null;

        根据传入的当前线程t返回它的局部变量threadLocals,而这个threadLocals实际上是ThreadLocalMap类型的,而ThreadLocalMap又是ThreadLocal的一个内部类。

      • ThreadLocalMap:

        static class ThreadLocalMap {
        
                /**
                 * The entries in this hash map extend WeakReference, using
                 * its main ref field as the key (which is always a
                 * ThreadLocal object).  Note that null keys (i.e. entry.get()
                 * == null) mean that the key is no longer referenced, so the
                 * entry can be expunged from table.  Such entries are referred to
                 * as "stale entries" in the code that follows.
                 */
                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类型的值作为key。

      • 接下来看get方法中返回的setInitialValue方法:

        /**
             * Variant of set() to establish initialValue. Used instead
             * of set() in case user has overridden the set() method.
             *
             * @return the initial value
             */
            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;
            }

        当map不为空时,将键值对存进去,当map为空时,重新创建一个map

      • createMap方法:

        扫描二维码关注公众号,回复: 3839312 查看本文章
        void createMap(Thread t, T firstValue) {
                t.threadLocals = new ThreadLocalMap(this, firstValue);
            }

到这里基本就可以看出ThreadLocal是如何为每个线程创建变量的副本的:

首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,key为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

  • 例子:
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());
    }
}

 这段代码的输出结果为: 

从运行结果看出:因为实例化了两个ThreadLocal变量,所以他们对各自的局部变量保存的副本值是不一样的,因此在两个线程中的执行并不会互相影响各自的变量值。

总结一下:

  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

  2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

  3)在进行get之前,必须先set,否则会报空指针异常;

      如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法,设置默认值。

    因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

3.值继承

使用InheritableThreadLocal类可以实现值的继承,让子线程从父线程中取得值。不过要注意,如果子线程在取得值的同时,父线程将InheritableThreadLocal中的值进行修改,那么子线程取到的还是原来的值。

https://www.cnblogs.com/dolphin0520/p/3920407.html

猜你喜欢

转载自blog.csdn.net/striveb/article/details/83412848