深入解析ThreadLocal

Posted zhixuChen200

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入解析ThreadLocal相关的知识,希望对你有一定的参考价值。

文章目录


前言

多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步。

同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreadLocal就可以做这件事情,虽然ThreadLocal并不是为了解决这个问题而出现的。


提示:以下是本篇文章正文内容,下面案例可供参考

一、ThreadLocal是什么?

ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存

二、ThreadLocal使用示例

代码如下(示例):

public class ThreadLocalTest 
    //(1)print函数
    static void print(String str) 
        //1.1 打印当前线程本地内存中localVariable变量的值
        System.out.println(str + ":" + localVariable.get());
        //1.2 清除当前线程本地内存中的localVariable变量
        localVariable.remove();
    

    //(2) 创建ThreadLocal变量
    static ThreadLocal<String> localVariable = new ThreadLocal<String>();

    public static void main(String[] args) 
        //(3) 创建线程one
        Thread threadOne = new Thread(new Runnable() 
            public void run() 
                //3.1 设置线程One中本地变量localVariable的值
                localVariable.set("threadOne local variable");
                //3.2 调用打印函数
                print("threadOne");
                //3.3 打印本地变量值
                System.out.println("threadOne remove after" + ":" + localVariable.get());
            
        );
        //(4) 创建线程two
        Thread threadTwo = new Thread(new Runnable() 
            public void run() 
                //4.1 设置线程Two中本地变量localVariable的值
                localVariable.set("threadTwo local variable");
                //4.2 调用打印函数
                print("threadTwo");
                //4.3 打印本地变量值
                System.out.println("threadTwo remove after" + ":" + localVariable.get());
            
        );
        //(5)启动线程
        threadOne.start();
        threadTwo.start();
    

三、解析ThreadLocal源码

1. void set(T value)

public void set(T value) 
        //(1)获取当前线程
        Thread t = Thread.currentThread();
        //(2)将当前线程作为key,去查找对应线程中的threadLocals变量,并设置值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
        //(3)第一次调用就创建当前线程的HashMap
            createMap(t, value);


set方法的流程已经很清晰了,我们要看看getMap(t)方法和createMap(t, value)做了什么可以使得每个线程有自己的本地变量。

ThreadLocalMap getMap(Thread t) 
        return t.threadLocals;


 void createMap(Thread t, T firstValue) 
        t.threadLocals = new ThreadLocalMap(this, firstValue);
 


class Thread implements Runnable 
     **
     **
     ** snip
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

  1. 从Thread类的源码中可以看到每个线程中都维护的一个 threadLocals 变量,初始的时候是null。
  2. 第一次调用就会去执行 createMap() 方法,方法中对 threadLocals 进行初始化,并且赋值
  3. getMap()方法,只是获取点前线程的 threadLocals 变量而已,其实看到这里就已经能够明白ThreadLocal为什么能够保证共享变量的安全性,其实就是一个套壳的工具类,去操作各个线程中的 threadLocals 变量。

2. T get()

    public T get() 
        //(1)获取当前线程
        Thread t = Thread.currentThread();
        //(2)获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //(3)如果threadLocals不为null,则返回对应本地变量的值
        if (map != null) 
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) 
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            
        
        //(4)如果threadLocals为null,则初始当前线程的threadLocals成员变量
        return setInitialValue();
    

通过上文对set()方法的讲解,这个代码一眼就能清楚流程,我们主要看看setInitialValue()方法做了什么。

  private T setInitialValue() 
       //(5)初始化为null
        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;
    

如果当前的线程 threadLocals 变量不为空,就设置线程当前的本地变量为null,否者去直接初始化threadLocals 变量,最后返回空。

3. void remove()

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

如以上代码所示,如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量。

四、ThreadLocal不支持继承性

1. 例子

public class TestThreadLocal 
    //(1)创建线程变量
    public static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public static void main(String[] args) 
        //(2) 设置线程变量
        threadLocal.set("hello world");
        //(3) 启动子线程
        Thread thread = new Thread(new Runnable() 
            public void run() 
                //(4) 子线程输出线程变量的值
                System.out.println("thread:" + threadLocal.get());
            
        );
        thread.start();
        //(5) 主线程输出线程变量的值
        System.out.println("main:" + threadLocal.get());
    

输出结果:
main:hello world
thread:null

也就是说,同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程为thread线程,而这里调用set方法设置线程变量的是main线程,两者是不同的线程,自然子线程访问时返回null。那么有没有办法让子线程能访问到父线程中的值?答案是有。

2. InheritableThreadLocal类

为了解决上节提出的问题,InheritableThreadLocal应运而生。InheritableThreadLocal继承自ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。下面看一下InheritableThreadLocal的代码。

public class InheritableThreadLocal<T> extends ThreadLocal<T> 
    //(1)
    protected T childValue(T parentValue) 
        return parentValue;
    
    //(2)
    ThreadLocalMap getMap(Thread t) 
       return t.inheritableThreadLocals;
    
   //(3)
    void createMap(Thread t, T firstValue) 
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    

由如上代码可知,InheritableThreadLocal继承了ThreadLocal,并重写了三个方法。由代码(3)可知,InheritableThreadLocal重写了createMap方法,那么现在当第一次调用set方法时,创建的是当前线程的inheritableThreadLocals变量的实例而不再是threadLocals。由代码(2)可知,当调用get方法获取当前线程内部的map变量时,获取的是inheritableThreadLocals而不再是threadLocals。

下面我们看一下重写的代码(1)何时执行,以及如何让子线程可以访问父线程的本地变量。这要从创建Thread的代码说起,打开Thread类的默认构造函数,代码如下。

  public Thread(Runnable target) 
     init(null, target, "Thread-" + nextThreadNum(), 0);
  
   private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc) 
        ...
        //(4)获取当前线程
        Thread parent = currentThread();
        ...
        //(5)如果父线程的inheritableThreadLocals变量不为null
        if (parent.inheritableThreadLocals ! = null)
        //(6)设置子线程中的inheritableThreadLocals变量
        this.inheritableThreadLocals =
  ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
     this.stackSize = stackSize;
     tid = nextThreadID();
  

总结:InheritableThreadLocal类通过重写代码(2)和(3)让本地变量保存到了具体线程的inheritableThreadLocals变量里面,那么线程在通过InheritableThreadLocal类实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面。

以上是关于深入解析ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

深入理解Stream之foreach源码解析

Enum深入解析

深入解析React

深入解析Java CAS底层实现原理

[转]毕设- 深入HBase架构解析

深入解析条件变量(condition variables)