细说ThreadLocal(一)

2021年11月22日 阅读数:3
这篇文章主要向大家介绍细说ThreadLocal(一),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

前言

java虚拟机在执行Java程序的过程当中会把它所管理的内存划分为若干个不一样的数据区域。以下图所示:java

其中堆是占虚拟机中内存最大的,堆被全部线程所共享,其最主要的即是存放实例对象。也由于堆内存是共享的,所以在多线程操做的条件下,多线程中堆内存中的数据十分容易发生线程安全的问题。所以为了保证多个线程对变量的安全访问,咱们能够将变量放到ThreadLocal对象中,变量在每一个线程中都有独立值,线程只能操做本身的变量,访问不到其余线程中的变量。编程

ThreadLocal

ThreadLocal顾名思义即是线程本地变量的意思,在JAVA程序中每new一个ThreadLocal对象实例时,每一个线程就会有一个隶属于本身的变量,一个专属于线程的变量,也所以该变量不会被其余的线程访问到,以此来规避了线程安全的问题。设计模式

那么ThreadLocal如何使得每一个线程拥有本身独有的本地值呢?api

在JDK8的版本中,每个线程都有一个属于本身的ThreadLocalMap,ThreadLocalMap随着Thread的建立而存在,随着Thread的实例销毁而销毁。数组

        //ThreadLocalMap其中一个构造函数
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

而ThreadLocalMap便是保存本地变量的关键之一,它首先以ThreadLocal实例和变量值做为Entry对象的构造参数来构造Entry对象,后以ThreadLocal实例进行散列计算hash,在散列函数计算后,每一个ThreadLocal会均匀地、独立地被分布在Entry数组中,也就是会获得本身在Entry数组中的索引值,而后用此索引将构造出来的Entry对象放入到Entry数组中。也因为每一个线程都有本身的ThreadLocalMap,所以变量值是存放在专属于本身线程的ThreadLocalMap中,这个ThreadLocalMap其余线程获取不到,因此每一个线程都有专属于本身的变量值,在操做的时候也是对本身专属的变量值进行操做。安全

从上图咱们也能够知道ThreadLocalMap实际上是由ThreadLocal来进行管理的,二者的关系密不可分。多线程

咱们在平时的操做中,大可能是操做ThreadLocal的get(),set()方法,彷佛ThreadLocalMap接触的较少,但在接下来深刻ThreadLocal的时候,咱们会发现ThreadLocal这个类实际上是基于ThreadLocalMap来完成的。并发

ThreadLocal的get方法

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

上面的代码块展现的是ThreadLocal中get方法的源码,经过源码咱们能够了解到get方法有如下步骤:函数

  1. 首先它会获取当前占有CPU时间片的线程的实例,而后经过当前线程的实例调用getMap()方法来获取当前线程的ThreadLocalMap。
 //getMap()方法
 ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
  1. 若是ThreadLocalMap不为空的话,就以自身ThreadLocal实例做为参数调用ThreadLocalMap的getEntry()方法来获取到Entry对象,若是Entry对象不为空,则获取Entry的value属性值返回。
  2. 若是map为空的话或者此ThreadLocal实例计算出的hash值最为Entry数组的索引在Entry数组中并未存在Entry对象,证实当前线程并未初始化ThreadLocalMap,调用setInitialValue方法后返回。

ThreadLocal的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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

咱们来探寻get()方法中调用的setInitialValue方法,在如下代码中咱们能够知道:高并发

  1. 首先一上来就会调用一个钩子函数initialValue()来给value变量赋值,可是咱们进入initialValue()方法却发现这个方法的返回值是null,若是须要继承ThreadLocal来重写这个方法就太麻烦,JDK已经为你们定义了ThreadLocal的内部SuppliedThreadLocal静态子类,而且提供了ThreadLocal.withInitial()静态工厂方法,咱们只须要在定义一个ThreadLocal类型变量时,使用这个方法。

initialValue钩子函数只会调用一次,且只在不使用ThreadLocal.set()方法去设置值就使用ThreadLocal.get()方法去获取值的时候,会执行。

      //钩子函数
      protected T initialValue() {
                return null;
          }

      //静态工厂方法
      public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
          return new SuppliedThreadLocal<>(supplier);
      }
  1. get方法的步骤一致,也是获取当前的线程后,获取当前线程的ThreadLocalMap,若是没有的话则建立为ThreadLocalMap建立一个map,这是由于一开始Thread下面的ThreadLocalMap初始值为空,因此有create这个步骤。在creatMap的方法中,咱们能够看到了新建了一个ThreadLocalMap类,并以当前的ThreadLocal实例对象和initialValue产生的值做为构造参数,以今生成Entry对象保存在Entry数组中。
       void createMap(Thread t, T firstValue) {
          t.threadLocals = new ThreadLocalMap(this, firstValue);
       }
  1. 最后方法返回。

ThreadLocal的set方法

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

以上是ThreadLocal的set方法的源码,它相对于get方法较简单,也是获取当前线程并获取当前线程的ThreadLocalMap,若是有的话调用ThreadLocalMap的set方法将值设置进去,若是ThreadLocal为空,则使用createMap方法去建立一个ThreadLocalMap。

ThreadLocal的remove方法

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null) {
             m.remove(this);
         }
     }

ThreadLocal的remove方法便更简单了,它仅判断获取到的当前线程的ThreadLocalMap不为空,则调用了ThreadLocalMap的remove方法去删除值。

后续

从以上的ThreadLocal函数,咱们能够看到,许多重要的方法都是依靠着ThreadLocalMap及其api去完成对ThreadLocal方法的实现的,不难看出理解ThreadLocalMap其实相较于理解ThreadLocal是比较重要的,而ThreadLocalMap内部对Entry这个子类的实现,更是考虑到了ThreadLocal的内存泄漏,所以使用了WeakReference弱引用去关联ThreadLocal实例,防止强引用致使的内存泄露的问题。

对ThreadLocalMap我会另开一个随笔去写,请多多担待。

结尾

本文参考

[1] 周志明.深刻理解Java虚拟机:JVM高级特性与最佳实践.-2版.北京:机械工业出版社,2013.6
[2] 尼恩.Java高并发编程.卷2,多线程、锁、JMM、JUC、高并发设计模式.北京:机械工业出版社,2021,5