随笔 - 632  文章 - 17  评论 - 54  阅读 - 93万

ThreadLocal原理分析

一、概述

  简单点来说ThreadLocal其实是一个数据存储类,通过get(),和set(key)来实现对数据的存取。并且可以在指定的线程中存储取数据,而在这个线程中存储的数据对于其他线程是访问不到的。

  什么情况下比较适合使用ThreadLocal来存取数据?

  答:当数据的作用域是线程或者不同的线程具有不同的数据副本的时候就可以考虑采用ThreadLocal来实现。

二、例子

  上面的一段话听起来可能有点抽象,下面举个栗子来具体的说明:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
   * 程序的入口main方法
   *
   * @param args
   */
  public static void main(String[] args) {
      final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
          @Nullable
          @Override
          protected String initialValue() {
              return "水果";
          }
      };
 
      threadLocal.set("苹果");//主线程中存储一个苹果
      String name1 = threadLocal.get();
      System.out.println("name1:" +Thread.currentThread().getName()+"-" +name1);
      new Thread() {
          @Override
          public void run() {
              threadLocal.set("香蕉");
              String name2 = threadLocal.get();
              System.out.println("name2:" +Thread.currentThread().getName()+"-"+ name2);
 
          }
      }.start();
      new Thread() {
          @Override
          public void run() {
              String name3 = threadLocal.get();
              System.out.println("name3:" +Thread.currentThread().getName()+"-"+ name3);
          }
      }.start();
  }

  

  上面这个小例子大家猜测一下最终输出的结果是啥?

  如果不考虑ThreadLocal的特性那么我们最终输出结果应该是:苹果、香蕉、香蕉

  一旦考虑了ThreadLocal的特性最终结果会输出:苹果、香蕉、水果(默认值),因为ThreadLocal的特性决定了其可以多个线程在互不干扰的情况下存取数据,也就是说,每一个set和get方法都是针对当前线程的。

  大家看下输出日志就明白了。如果还觉得有疑问,那接着往下看,等下分析源代码的时候对其理解就会更加清晰:

1
2
3
name1:main-苹果
name2:Thread-0-香蕉
name3:Thread-1-水果

  

三、源代码分析

  我们还是开始从ThreadLocal的使用开始着手

  1.ThreadLocal.set(key)  

1
2
3
4
5
6
7
8
public void set(T value) {<br>    //获取当前线程对象
      Thread t = Thread.currentThread();<br>    //获取当前线程中的ThreadLocalMap对象
      ThreadLocalMap map = getMap(t);<br>     //判断map是否为空,空则创建后存数据,不为空则直接存数据
      if (map != null)
          map.set(this, value);
      else
          createMap(t, value);
  }

  在set方法中首先会调用Thread.currentThread()方法获取当前Thread对象,然后从当前Thread对象中获取一个ThreadLocalMap集合,如果集合不为空就把ThreadLocal为key,传入的参数为value存入map集合。如果为空则创建集合后再存入。

  1.1.接下来看看getMap(currentThread)都干了写啥 

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

  getMap就干了一件事情,把当前线程中的ThreadLocalMap取出来。threadLocals是线程内部的一个成员变量,其形式是ThreadLocal.threadLocals。下面是Thread源码中threadLocals的表现

1
2
3
4
5
6
7
public
class Thread implements Runnable {
     ....省略了之前的代码
 
    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

  所以整个getMap方法其实就是把当前Thread对象中的ThreadLocalMap集合取出来。

  1.2.我们再次回到set方法中的createMap方法 

1
2
3
void createMap(Thread t, T firstValue) {
       t.threadLocals = new ThreadLocalMap(this, firstValue);
   }

  由以上代码可知,createMap方法其实就是创建一个ThreadLocalMap对象并赋值给当前线程的threadLocals。

  1.3.ThreadLocalMap对象 

1
2
3
4
5
6
7
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的构造函数中会初始化一个Entry数组,并初始化数组的长度,默认可以存放16个元素。然后初始化一个entry对象把ThreadLocal和value当键值对传入,最后把entry对象放入table数组。

 总结:ThreadLocal的set方法会从当前线程总拿到一个ThreadLocal.ThreadLocalMap集合,并将ThreadLocal当做key,要存的数据当做value存入map集合。由于是当前线程的map集合,所以此集合对于其他线程是不可见的。

  2.ThreadLocal.get()  

1
2
3
4
5
6
7
8
9
10
11
12
13
public T get() {<br>     //获取当前线程对象
      Thread t = Thread.currentThread();<br>     //获取当前线程的ThreadLocalMap对象
      ThreadLocalMap map = getMap(t);
      if (map != null) {<br>       //根据ThreadLocal实例获取一个Entry对象,并从entry对象中取出value值
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
              @SuppressWarnings("unchecked")
              T result = (T)e.value;
              return result;
          }
      }<br>     //如果Map对象为空,就返回初始化的值,上面的小例子中如果map为空则返回一个”水果“的字符串
      return setInitialValue();
  }

  总结:ThreadLocal.get()方法首先会拿到当前线程的ThreadLocal.ThreadLocalMap集合,然后以ThreadLocal实例为key从map集合中取出一个entry对象,然后获取entry对象的值并返回。从上面的逻辑可以看出ThreadLocal操作的依然是当前线程的ThreadLocalMap对象。这也就是为什么ThreadLocal能够在不同线程中存取数据,且不同的线程具有不同的数据副本。

  

  几个get和set来个大总结:

  1.set方法存数据是在当前线程的ThreadLocalMap对象中。

  2.get方法取数据也是从当前线程的ThreadLocalMap对象中取。

  3.每个线程只能在自己的线程内部操作ThreadLocalMap,所以不同的线程使用ThreadLocal会得到不同的数据副本(相互独立)  

 

  

posted on   飘杨......  阅读(189)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
历史上的今天:
2014-03-16 java中使用阻塞队列实现生产这与消费这之间的关系
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示