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

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,是Thread Local Variable(线程局部变量)的意思,也许将它命名为ThreadLocalVar更加合适. 线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突.从线程的角度看,就好像每一个线程都完全拥有该变量. ThreadLocal类的

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类型的变量本身的确是一个,这才是本质!  

有关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用法.分享给大家供大家参考,具体如下: 目录 ThreadLocal的基本使用 ThreadLocal实现原理 源码分析(基于openjdk11) get方法: setInitialValue方法 getEntry方法 set方法 ThreadLocalMap的set方法 replaceStaleEntry方法 cleanSomeSlots方法 rehash方法 expungeStaleEntries方法 resize方法 ThreadLocal实现

Java List 用法实例详解

Java List 用法实例详解 Java中可变数组的原理就是不断的创建新的数组,将原数组加到新的数组中,下文对Java List用法做了详解. List:元素是有序的(怎么存的就怎么取出来,顺序不会乱),元素可以重复(角标1上有个3,角标2上也可以有个3)因为该集合体系有索引 ArrayList:底层的数据结构使用的是数组结构(数组长度是可变的百分之五十延长)(特点是查询很快,但增删较慢)线程不同步 LinkedList:底层的数据结构是链表结构(特点是查询较慢,增删较快) Vector:底层

java return用法实例详解

return用法总结: 一.在有返回值的方法中,返回方法指定类型的值,同时结束方法执行: 二.也可以用在返回值为void的方法中,用来终止方法运行: break和return break是用来跳出循环的,例如for,while,do-while都可以跳出,但不跳出函数 return是使整个函数返回的,后面的不管是循环里面还是循环外面的都不执行 1.可以在具有对象引用返回类型的方法中返回null public Button doStuff(){ return null: } 2.数组是完全合法的返

Java中的instanceof关键字在Android中的用法实例详解

在下面介绍Android中如何使用instanceof关键字开发更方便时,先来温习一下java中instanceof的概念. instanceof大部分的概念是这样定义的:instanceof是Java的一个二元操作符,和==,>,<是同一类东西.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据.举个栗子: String s = "I AM an Object!"; boolean isObj

java中静态导入机制用法实例详解

java中静态导入机制用法实例详解 这里主要讲解了如何使用Java中静态机制的用法,这里提供了简单实例大家可以参考下. 静态常量类 在java开发中,我们会经常用到一些静态常量用于状态判断等操作.为了能够在多个地方复用这些常量,通常每个模块都会加一个常量类,举个简单的列子: import com.sky.OrderMouleConsstants; /** * Created by gantianxing on 2017/4/21. */ public class Test { public vo

Java设计模式之策略模式原理与用法实例详解

本文实例讲述了Java设计模式之策略模式原理与用法.分享给大家供大家参考,具体如下: 策略模式定义了一系列的算法,并将每一个算法封装起来,而且使它们还可以相互替换.策略模式让算法独立于使用它的客户而独立变化.其中JDK里面的TreeSet类和TreeMap类就用到了策略模式.这两个类是带排序的集合类,其中排序的规则就相当于策略模式里定义的一系列算法,而集合类就相当于是策略模式里的环境类,供用户使用,用只知道TreeSet和TreeMap是带排序的,至于怎么排序的,是由排序的算法决定的. 策略模式

Java设计模式之装饰模式原理与用法实例详解

本文实例讲述了Java设计模式之装饰模式原理与用法.分享给大家供大家参考,具体如下: 装饰模式能在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能.它是通过创建一个包装对象,也就是装饰来包裹真实的对象.JDK中IO的设计就用到了装饰模式,通过过滤流对节点流进行包装来实现功能的扩展. 装饰模式的角色的组成: ① 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加工功能的对象.(InputStream.OutputStream) ② 具体构件(Concrete Co

java 抽象类的实例详解

java 抽象类的实例详解 前言: 什么是抽象类?这名字听着就挺抽象的,第一次听到这个名字还真有可能被唬住.但是,就像老人家所说的,一切反动派都是纸老虎,一切有着装x名字的概念也是纸老虎.好吧,我们已经从战略上做到了藐视它,现在就要战术上重视它,如同要解决纸老虎,就要一个牙齿一个牙齿地敲,一个爪子一个爪子地拔:解决这种抽象概念也一样,先要把它具体化,细分化,然后一个一个地来. 我一般遇到新的概念都会问三个问题: 1.这个东西有什么用?用来干什么的?它的意义在哪里?(显然,如果是没用的东西,就没必

MyBatis动态SQL标签用法实例详解

1.动态SQL片段 通过SQL片段达到代码复用 <!-- 动态条件分页查询 --> <sql id="sql_count"> select count(*) </sql> <sql id="sql_select"> select * </sql> <sql id="sql_where"> from icp <dynamic prepend="where&quo

Angular中$cacheFactory的作用和用法实例详解

先说下缓存: 一个缓存就是一个组件,它可以透明地储存数据,以便以后可以更快地服务于请求.多次重复地获取资源可能会导致数据重复,消耗时间.因此缓存适用于变化性不大的一些数据,缓存能够服务的请求越多,整体系统性能就能提升越多. $cacheFactory介绍: $cacheFactory是一个为Angular服务生产缓存对象的服务.要创建一个缓存对象,可以使用$cacheFactory通过一个ID和capacity.其中,ID是一个缓存对象的名称,capacity则是描述缓存键值对的最大数量. 1.