关于java:JDK成长记11ThreadLocal-上

2次阅读

共计 9387 个字符,预计需要花费 24 分钟才能阅读完成。

上一节你应该学习了 thread 的基本知识和源码原理,相熟了线程的利用场景。这一节来学习下和 Thread 相干的一个类,ThreadLocal。

什么是 ThreadLocal?

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> 什么是 ThreadLocal?</span></h3></div>

字面意思是线程本地变量的意思。用一句话解释就是:线程本地的变量正本,属于每个线程本人独有的。

为什么说是变量正本呢?因为每个线程应用 ThreadLocal 设置本人的值,设置的值相互之间不受影响,然而应用的是同一个 ThreadLocal 对象。所以设置的每个变量,是给每个线程一个独有的变量正本。

你能够画一个图来了解下:

当你晓得了什么是 ThreadLocal 后,让咱们简略来应用一下它,看下他的应用成果。

Hello ThreadLocal

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> Hello ThreadLocal</span></h3></div>

上面通过一段 Hello ThreadLocal 小程序,让你回顾下 ThreadLocal 的应用。假如有这么一个场景:

线程启动了 2 个线程,应用 threadLocal 设置了一个 Loan 对象,main 线程也设置了本人的 loan 对象。线程 2 和 main 线程在设置前尝试拜访 threadLocal 中的数据。

代码实现如下:

public class HelloThreadLocal {private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>();

  public static void main(String[] args) {
    // 线程 1 应用 threadLocal 设置本人的变量正本
    new Thread(() -> {threadLocal.set(new Loan("zhangsan", "1000.00"));
      System.out.println("线程 -1loan:"+threadLocal.get());

    }).start();


    // 线程 2 应用 threadLocal 设置本人的变量正本
    new Thread(() -> {
   try {Thread.sleep(1 * 1000);
      } catch (InterruptedException e) { }

      HelloThreadLocal.Loan loan = threadLocal.get();
      System.out.println("线程 -2loan:"+loan);

      threadLocal.set(new Loan("lisi", "2000.00"));
      loan = threadLocal.get();
      System.out.println("线程 -2loan:"+loan);
    }).start();


    try {Thread.sleep(5 * 1000);
    } catch (InterruptedException e) { }

    System.out.println("main- 线程 loan:"+threadLocal.get());
    threadLocal.set(new Loan("wangwu", "1000.00"));
    System.out.println("main- 线程 loan:"+threadLocal.get());

  }

  @Data
  @AllArgsConstructor
  public static class Loan {

    private String name;

    private String amount;

  }

}

下面的代码输入后果如下:

线程 -1loan:HelloThreadLocal.Loan(name=zhangsan, amount=1000.00)

线程 -2loan:null

线程 -2loan:HelloThreadLocal.Loan(name=lisi, amount=2000.00)

main- 线程 loan:null

main- 线程 loan:HelloThreadLocal.Loan(name=wangwu, amount=1000.00)

能够看出,每个线程无奈获取到其余线程设置的 loan 对象,哪怕是应用同一个 ThreadLocal 设置的。为什么会这样呢?其实就是因为每个线程的变量正本,ThreadLocal 只是一个工具,操作了线程本地的变量正本而已。具体原理如下图所示:

ThreadLocal 源码分析 get 办法脉络

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> ThreadLocal 源码分析 get 办法脉络 </span></h3></div>

置信你通过下面的例子,曾经了解 ThreadLocal 的作用了。它的底层是如何做到的呢?你须要剖析一下它的源码了。

这里咱们把栗子简化下,能够更好的剖析 get、set 办法的源码。简化 HelloThreadLocal 代码如下:

public class ThreadLocalGetMethod {private static ThreadLocal<Loan> threadLocal = new ThreadLocal<ThreadLocalGetMethod.Loan>();


  public static void main(String[] args) {
   // 线程 1
   new Thread(() -> {System.out.println("线程 -1loan:"+threadLocal.get()); // 输入 null
   }).start();}

 

  @Data
  @AllArgsConstructor
  public static class Loan {

   private String name;

   private String amount;

  }

} 

依照下面的例子,你先 new 了一个 ThreadLocal 对象,所以须要看下 ThreadLocal 构造函数,做了什么事件没有,很显著什么都没做。

  public ThreadLocal() {}

之后线程 1 会间接执行了 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();} 

你能够看到,下面的 get 办法的脉络次要如下:

1. 获取以后线程的一个变量 ThreadLocalMap

2. 如果 map 为空调用 setInitialValue 返回默认值,并创立 map

3. 如果 map 非空获取 entry 中 key 的对应的 value 值

你能够先画一个图,之后再来别离看下每一步。threadLocal 的 get 办法外围脉络如图所示:

ThreadLocal 源码分析 get 办法细节

<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> ## ThreadLocal 源码分析 get 办法细节 </span></h3></div>

这个是总的脉络图,接下来看一下每一步的细节。

1、获取以后线程的一个变量 ThreadLocalMap。

 public T get() {Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // 临时省略
}

这两句代码第一句是获取到以后运行的线程对象,第二句获取了如下 map,能够从正文看进去,理论就是获取了 thread 对象 t 的一个属性,这属性是一个 ThreadLocalMap。代码如下:

  ThreadLocalMap getMap(Thread t) {return t.threadLocals;}        

不晓得各位还记得上一节的 thread 创立的场景么?当中有一些细节并没有讲,thread 除了状态、名字、线程 id 以外,还有两个比拟要害的属性,threadLocals 和 inheritableThreadLocals。代码如下:

  public class Thread implements Runnable {//......( 其余源码)
     /* 
     * ThreadLocal 应用,以后线程的 ThreadLocalMap,次要存储该线程本身的 ThreadLocal
     */
     ThreadLocal.ThreadLocalMap threadLocals = null;
   
     /*
      * InheritableThreadLocal 应用,自父线程继承而来的 ThreadLocalMap,* 次要用于父子线程间 ThreadLocal 变量的传递
      */
     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    //......(其余源码)
   }

正文写的分明,一个用于是父子线程传递的变量正本 Map,个别是 InheritableThreadLocal 才会应用,一个是本人线程变量正本 Map 个别 ThreadLocal 应用。

上一节还有一个细节我没有讲,inheritableThreadLocals 这个变量在创立线程的时候调用 init 办法的时候会判断,如果父线程有值复制到子线程一份。代码如下:

/**
   * 初始化一个线程. 此函数有两处调用,* 1、下面的 init(),不传 AccessControlContext,inheritThreadLocals=true
   * 2、传递 AccessControlContext,inheritThreadLocals=false
   */
  private void init(ThreadGroup g, Runnable target, String name,
    long stackSize, AccessControlContext acc,
    boolean inheritThreadLocals) {
    //......(其余代码)if (inheritThreadLocals && parent.inheritableThreadLocals != null)
    this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    //......(其余代码)}

inheritableThreadLocals 这个变量的利用在申请跟踪,传递 traceId 的时候能够被用到。这里不做过多关注,外围还是关注 threadLocals 这个根本的线程变量正本。

你能够看下整个 Map 是什么?是一个 ThreadLocalMap,它是 ThreadLocal 的外部类。所以你能够失去如下图所示论断:

回过头来再看下,这两行代码,十分要害的一点就是,尽管应用了 ThreadLocal 的 get,然而操作的理论是以后线程的 threadLocals 本地变量正本的 Map,这一点是很重要的。

 public T get() {Thread t = Thread.currentThread();

    ThreadLocalMap map = getMap(t);

    // 临时省略

}

getMap 办法执行实现后,流程如下图所示:

2、如果 map 为空,调用 setInitialValue 返回默认值,并创立 map

因为以后线程的获取到正本变量 map 为 null,所以会执行到 setInitialValue 这个分支,如下所示:

所以你要来看下 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);
    return value;
  }

你看完这个代码后,能够发现这个办法外围脉络做了两件事件,一个是初始化值,一个是创立 map。

如下图所示:

首先下面第一句代码是调用了 initialValue 办法,从名字上看就是一个初始化的动作,能够看下它的源码,非常简单:

  protected T initialValue() {return null;}

默认返回 null,能够通过重写这个办法或者一个匿名外部类(jdk1.8) 来设置一个初始值。

这里我给出大家设置初始值的形式。

应用重写 initialValue 形式

  private static ThreadLocal<Loan> threadLocal =  new ThreadLocal<Loan>() {@Override public Loan initialValue() {return new Loan("默认值", "1000.00");
    }
  };

或者应用 withInitial,匿名外部类

private static ThreadLocal<Loan> threadLocal = ThreadLocal.withInitial(() -> new Loan("默认值", "1000.00"));  

setInitialValue 执行到这里,逻辑很简略,如下:

接着进行了 if 判断,以后线程的本地变量正本 threadLocals 通过 getMap 获取到的必定默认是 null,所以会执行创立 Map 如下图:

所以会创立 map,执行如下代码

  void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
  }
    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);
    }

下面这个创立 map 的细节咱们不过来深刻了,ThreadLocalMap 不是咱们要讲的重点,有趣味的同学能够看下他的源码,这里给出 ThreadLocalMap 外围点:

  • 底层层是数组,默认大小 16,默认扩容阈值 10,扩容阈值计算形式:threshold = len * 2 / 3= 10
  • key 是 threadLocal,vlaue 是 ThreadLocal 的泛型对象
  • 寻址算法 firstKey.threadLocalHashCode & (INITIAL_CAPACITY – 1);
  • hash 值算法:firstKey.threadLocalHashCode,底层是用了 AtomicInteger 和一个增量值 HASH_INCREMENT,保障
  • Hash 抵触应用凋谢寻址法(HashMap 是单链表法)
  • 凋谢寻址的外围是地位有元素了就换地位

这里 value 应该是为 null,因为 initialValue 办法没有指定初始化值。

最初咱们回到 get 源码的脉络图。通过 getMap()、setInitialValue() 办法调用后,最终线程 threadLocal.get() 会输入 null 的值。如下所示:

好了,明天 ThreadLocal 就学习到这里,下一节咱们来摸索下:

  • ThreadLocal 的 set 源码原理
  • JVM 的中的强援用、弱援用、软援用、虚援用
  • 弱援用在 ThreadLocal 的利用
  • ThreadLocal 内存透露问题剖析
  • ThreadLocal 利用场景举例

    本文由博客一文多发平台 OpenWrite 公布!

正文完
 0