Java源码解析ThreadLocal及使用场景

ThreadLocal是在多线程环境下经常使用的一个类。

这个类并不是为了解决多线程间共享变量的问题。举个例子,在一个电商系统中,用一个Long型变量表示某个商品的库存量,多个线程需要访问库存量进行销售,并减去销售数量,以更新库存量。在这个场景中,是不能使用ThreadLocal类的。

ThreadLocal适用的场景是,多个线程都需要使用一个变量,但这个变量的值不需要在各个线程间共享,各个线程都只使用自己的这个变量的值。这样的场景下,可以使用ThreadLocal。此外,我们使用ThreadLocal还能解决一个参数过多的问题。例如一个线程内的某个方法f1有10个参数,而f1调用f2时,f2又有10个参数,这么多的参数传递十分繁琐。那么,我们可以使用ThreadLocal来减少参数的传递,用ThreadLocal定义全局变量,各个线程需要参数时,去全局变量去取就可以了。

接下来我们看一下ThreadLocal的源码。首先是类的介绍。如下图。这个类提供了线程本地变量。这些变量使每个线程都有自己的一份拷贝。ThreadLocal期望能够管理一个线程的状态,例如用户id或事务id。例如下面的例子产生线程本地的唯一id。线程的id是第一次调用时进行复制,并且在后面的调用中保持不变。

This class provides thread-local variables.
These variables differ from their normal counterparts in that each thread that accesses
 one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that
 wish to associate state with a thread (e.g., a user ID or Transaction ID).
For example, the class below generates unique identifiers local to each thread.
 A thread's id is assigned the first time it invokes ThreadId.get() and
remains unchanged on subsequent calls.
  import java.util.concurrent.atomic.AtomicInteger;
  public class ThreadId {
    // Atomic integer containing the next thread ID to be assigned
    private static final AtomicInteger nextId = new AtomicInteger(0);
    // Thread local variable containing each thread's ID
    private static final ThreadLocal<Integer> threadId =
      new ThreadLocal<Integer>() {
        @Override protected Integer initialValue() {
          return nextId.getAndIncrement();
      }
    };
    // Returns the current thread's unique ID, assigning it if necessary
    public static int get() {
      return threadId.get();
    }
  }
Each thread holds an implicit reference to its copy of a thread-local
variable as long as the thread is alive and the ThreadLocal instance is
accessible; after a thread goes away, all of its copies of thread-local
 instances are subject to garbage collection (unless other references to
these copies exist).

下面看一下set方法。

set方法的作用是,把线程本地变量的当前线程的拷贝设置为指定的值。大部分子类无需重写该方法。首先获取当前线程,然后获取当前线程的ThreadLocalMap。如果ThreadLocalMap不为null,则设置当前线程的值为指定的值,否则调用createMap方法。

获取线程的ThreadLocalMap对象,是直接返回的线程的threadLocals,类型为ThreadLocalMap。也就是说,每个线程都有一个ThreadLocalMap对象,用于保存该线程关联的所有的ThreadLocal类型的变量。ThreadLocalMap的key是ThreadLocal,value是该ThreadLocal对应的值。具体什么意思呢?在程序中,我们可以定义不止一个ThreadLocal对象,一般会有多个,比如定义3个ThreadLocal<String>,再定义2个ThreadLocal<Integer>,而每个线程可能都需要访问全部这些ThreadLocal的变量,那么,我们用什么数据结构来实现呢?当然,最好的方式就是像源码中的这样,每个线程有一个ThreadLocalMap,key为ThreadLocal变量名,而value为该线程在该ThreadLocal变量的值。这个设计实在是太巧妙了。

写到这里,自己回想起之前换工作面试时,面试官问自己关于ThreadLocal的实现原理。那个时候,为了准备面试,自己只在网上看了一些面试题,并没有真正掌握,在回答这个问题时,我有印象,自己回答的是用一个map,线程的id值作为key,变量值作为value,诶,露馅了啊。

  /**
   * Sets the current thread's copy of this thread-local variable
   * to the specified value. Most subclasses will have no need to
   * override this method, relying solely on the {@link #initialValue}
   * method to set the values of thread-locals.
   * @param value the value to be stored in the current thread's copy of
   *    this thread-local.
   **/
  public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
      map.set(this, value);
    else
      createMap(t, value);
  }
  /**
   * Get the map associated with a ThreadLocal. Overridden in
   * InheritableThreadLocal.
   * @param t the current thread
   * @return the map
   **/
  ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
  }
  /**
   * Create the map associated with a ThreadLocal. Overridden in
   * InheritableThreadLocal.
   * @param t the current thread
   * @param firstValue value for the initial entry of the map
   **/
  void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
  }

接下来看一下get方法。

源码如下。首先获取当前线程的ThreadLocalMap,然后,从ThreadLocalMap获取该ThreadLocal变量对应的value,然后返回value。如果ThreadLocalMap为null,则说明该线程还没有设置该ThreadLocal变量的值,那么就返回setInitialValue方法的返回值。其中的initialValue方法的返回值,通常情况下为null。但是,子类可以重写initialValue方法以返回期望的值。

  /**
   * 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();
  }
  /**
   * 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;
  }
  protected T initialValue() {
    return null;
  }

文章的最后,简单介绍一下ThreadLocalMap这个类,该类是ThreadLocal的静态内部类。它对HashMap进行了改造,用于保存各个ThreadLocal变量和某线程的该变量的值的映射关系。每个线程都有一个ThreadLocalMap类型的属性。ThreadLocalMap中的table数组的长度,与该线程访问的ThreadLocal类型变量的个数有关,而与别的无关。

This is the end。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。如果你想了解更多相关内容请查看下面相关链接

时间: 2019-01-05

有关ThreadLocal的面试题你真的懂了吗

说明 面试官:讲讲你对ThreadLocal的一些理解. 那么我们该怎么回答呢????你也可以思考下,下面看看零度的思考: ThreadLocal用在什么地方? ThreadLocal一些细节! ThreadLocal的最佳实践! 思考 ThreadLocal用在什么地方? 讨论ThreadLocal用在什么地方前,我们先明确下,如果仅仅就一个线程,那么都不用谈ThreadLocal的,ThreadLocal是用在多线程的场景的!!! ThreadLocal归纳下来就2类用途: 保存线程上下文信

ThreadLocal原理及内存泄漏原因

ThreadLocal有两个问题: 1. 每个变量副本是存储在哪了? 2. 变量副本是怎样从共享的变量中赋值出来的?源码中threadlocal的初始值是什么时候设置的. ThreadLocal为每个线程维护一个变量的副本? 每个线程的ThreadLocalMap都是线程自身持有的,但是初始化是在ThreadLocal中,然后每个线程相当于保存了一个map 这个map存的key是LocalThread的实例,value是存储的线程的局部变量 get方法 根据当前线程获取Thread中的值 set

java 中ThreadLocal 的正确用法

java 中ThreadLocal 的正确用法 用法一:在关联数据类中创建private static ThreadLocalThreaLocal的JDK文档中说明:ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread.如果我们希望通过某个类将状态(例如用户ID.事务ID)与线程关联起来,那么通常在这个类中定义private s

深入解析Java中ThreadLocal线程类的作用和用法

ThreadLocal与线程成员变量还有区别,ThreadLocal该类提供了线程局部变量.这个局部变量与一般的成员变量不一样,ThreadLocal的变量在被多个线程使用时候,每个线程只能拿到该变量的一个副本,这是Java API中的描述,通过阅读API源码,发现并非副本,副本什么概念?克隆品? 或者是别的样子,太模糊.   准确的说,应该是ThreadLocal类型的变量内部的注册表(Map<Thread,T>)发生了变化,但ThreadLocal类型的变量本身的确是一个,这才是本质!  

java多线程编程之InheritableThreadLocal

InheritableThreadLocal的作用: 当我们需要在子线程中使用父线程中的值得时候我们就可以像使用ThreadLocal那样来使用InheritableThreadLocal了. 首先我们来看一下InheritableThreadLocal的jdk源码: package java.lang; import java.lang.ref.*; public class InheritableThreadLocal<T> extends ThreadLocal<T> { p

java ThreadLocal使用案例详解

本文借由并发环境下使用线程不安全的SimpleDateFormat优化案例,帮助大家理解ThreadLocal. 最近整理公司项目,发现不少写的比较糟糕的地方,比如下面这个: public class DateUtil { private final static SimpleDateFormat sdfyhm = new SimpleDateFormat( "yyyyMMdd"); public synchronized static Date parseymdhms(String

Java ThreadLocal用法实例详解

本文实例讲述了Java ThreadLocal用法.分享给大家供大家参考,具体如下: 目录 ThreadLocal的基本使用 ThreadLocal实现原理 源码分析(基于openjdk11) get方法: setInitialValue方法 getEntry方法 set方法 ThreadLocalMap的set方法 replaceStaleEntry方法 cleanSomeSlots方法 rehash方法 expungeStaleEntries方法 resize方法 ThreadLocal实现

Java ThreadLocal类应用实战案例分析

本文实例讲述了Java ThreadLocal类应用.分享给大家供大家参考,具体如下: 一 点睛 ThreadLocal,是Thread Local Variable(线程局部变量)的意思,也许将它命名为ThreadLocalVar更加合适. 线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突.从线程的角度看,就好像每一个线程都完全拥有该变量. ThreadLocal类的

Java System类用法实战案例

本文实例讲述了Java System类用法.分享给大家供大家参考,具体如下: 一 使用System类访问系统属性 1 代码 import java.util.Map; public class SystemClassDemo { public static void main(String[] args) { Map<String, String> env = System.getenv(); // 获得系统的环境变量 for (String name : env.keySet()) { Sy

Java super关键字用法实战案例分析

本文实例讲述了Java super关键字用法.分享给大家供大家参考,具体如下: 一 点睛 如果子类继承了父类的数据成员,这时就需要调用父类的有参构造方法,来初始化来自父类的数据成员,这时就需要显示的调用父类中的有参构造方法super(参数1,参数2). 二 实战--super调用父类中的构造方法 1 代码 class Person { String name; int age; public Person( String name, int age ) // 父类的构造方法 { this.nam

Java线程池用法实战案例分析

本文实例讲述了Java线程池用法.分享给大家供大家参考,具体如下: 一 使用newSingleThreadExecutor创建一个只包含一个线程的线程池 1 代码 import java.util.concurrent.*; public class executorDemo { public static void main( String[] args ) { ExecutorService executor = Executors.newSingleThreadExecutor(); ex

Java匿名类,匿名内部类实例分析

本文实例讲述了Java匿名类,匿名内部类.分享给大家供大家参考,具体如下: 本文内容: 内部类 匿名类 首发日期 :2018-03-25 内部类: 在一个类中定义另一个类,这样定义的类称为内部类.[包含内部类的类可以称为内部类的外部类] 如果想要通过一个类来使用另一个类,可以定义为内部类.[比如苹果手机类,苹果手机类中的黄金版的是特别定制的] 内部类的外部类的成员变量在内部类中仍然有效,内部类中的方法也可以调用外部类中的方法.[不论是静态还是非静态的,内部类都可以直接调用外部类中的属性,] 内部

Java中包的概念和用法实战案例分析

本文实例讲述了Java中包的概念和用法.分享给大家供大家参考,具体如下: 一 点睛 1 package的声明方法: package package名称[.package名称2.package名称3]; 2 包的导入方法如下: import package 包名称.类名称 3 如果一个项目之中有几百个类,一个个导入会比较麻烦,为了方便导入,可以使用"包名.*"的形式完成,例如: import java.io.*; 这里的"*"是通配符,表示该"包名"

Java中内部类使用方法实战案例分析

本文实例讲述了Java中内部类使用方法.分享给大家供大家参考,具体如下: 一 点睛 分三种情况讨论内部类的使用: 1 在外部类内部使用内部类 2 在外部类以外使用非静态内部类 3 在外部类以外使用静态内部类 二 在外部类内部使用内部类 可以直接通过内部类的类名来定义变量,通过new调用内部构造器来创建实例. 不要在外部类的静态成员(包括静态方法和静态初始化块)中使用非静态内部类,因为静态成员不能访问非静态成员. 三 在外部类以外使用非静态内部类 1 点睛 private 修饰的内部类只能在外部类

Java static方法用法实战案例总结

本文实例讲述了Java static方法用法.分享给大家供大家参考,具体如下: 一 点睛 用static可以声明一个静态属性变量,其实,也可以用来声明方法,用它声明方法时也称为"类方法".使用static定义的方法可以由类名直接调用. static的main方法可以接收一个String类型的数组参数,该数组中保存执行Java命令时传递给所运行的类的参数.格式如下: java 类名称 参数1 参数2 参数3 二 实战--静态方法的声明 1 代码 class Person { String

Java SpringMVC实现国际化整合案例分析(i18n)

所谓国际化就是支持多种语言,web应用在不同的浏览环境中可以显示出不同的语言,比如说汉语.英语等.下面我将以具体的实例来举例说明: (1)新建动态Java web项目,并导入几个SpringMVC必需的几个jar包,项目结构图和所需jar包如下: (2)配置web.xml: <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-ins

Java二维数组实战案例

本文实例讲述了Java二维数组.分享给大家供大家参考,具体如下: 一 点睛 1 每行的元素个数不同的二维数组 Java定义二维数组更加灵活,允许二维数组中的每行的元素个数不相等,这点与其他编程语言不同,例如下面这个二维数组,其中第1行有4个元素,第2行有3个元素,第3行有5个元素 int [][] num={ {1,2,3,4}, {1,2,3}, {1,2,3,4,5} }; 下面的语句是声明整型的二维数组num并分配空间,其中第1行4个元素,第2行3个元素,第3行5个元素 int [][]