java.lang.ThreadLocal实现原理和源码分析

1、ThreadLocal的原理:为每一个线程维护变量的副本。某个线程修改的只是自己的副本。

2、ThreadLocal是如何做到把变量变成副本的呢?不是clone,而是简单地new。具体一点就是对于变量A,每一个线程都会执行一个初始化方法initialValue(),这个方法在ThreadLocal类的源码中只是返回null(下面代码),需要用户覆写,也就是在这个方法里面new一个A并返回,这个就是我们当前线程的副本。具体分析如下:

protected T initialValue() {return null;
}

我们看一个例子:我们顺着例子中的main函数的执行过程来作为我们分析的思路

import java.util.ArrayList;
import java.util.List;/*** Created by liangyh on 2/25/16.*/
public class ThreadLocalTest {private ThreadLocal<List<String>> threadLocal = new ThreadLocal<List<String>>(){@Overrideprotected List<String> initialValue() {List<String> list = new ArrayList<>();list.add("a");return list;}};public String get(int index){return threadLocal.get().get(index);}public void add(String value){threadLocal.get().add(value);}public synchronized void print(){List<String> list = threadLocal.get();if(list != null){for (int i = 0; i <list.size(); i++) {System.out.print(list.get(i) + "; ");}System.out.println();}}public static void main(String args[]) throws InterruptedException {ThreadLocalTest test = new ThreadLocalTest();test.print();MyThread thread1 = new MyThread(test);MyThread thread2 = new MyThread(test);MyThread thread3 = new MyThread(test);new Thread(thread1).start();new Thread(thread2).start();new Thread(thread3).start();//让主线程休眠半秒,Thread.sleep(500);//主线程中的变量没有改变,为a。test.print();}public static class MyThread implements Runnable{private ThreadLocalTest data;public MyThread(ThreadLocalTest data){this.data = data;}@Overridepublic void run() {String name = Thread.currentThread().getName();data.add(name);if(this.data != null){data.print();}}}
}

1、我们先实例化一个ThreadLocalTest对象,然后调用print()方法。在print()方法中第一行调用了threadLocal.get()方法。返回的是当前线程的副本,现在的线程是主线程。get()方法干了什么工作了?

先看get方法的源码!它首先获得当前线程的对象t,然后根据t作为参数得到一个map,这个map我们下面会讲到,它的key是ThreadLocal对象,value是变量副本。如果得到的map不为空,则返回map中的value值,如果为空,进入setInitialValue()方法中。在这个get方法中我们还应该注意到一个方法,getMap(Threadt),它返回的是Thread对象的一个属性ThreadLocal.ThreadLocalMapthreadLocals =null;这个属性的作用是保存这个线程的变量副本的。ThreadLocalMap便是上面提到的map。刚开始的时候,这个threadLocals变量是空的,什么时候赋值呢?暂时不管,先把思路回到get方法中的最后一行代码。

 /*** 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)return (T)e.value;}return setInitialValue();}
<pre name="code" class="java">   /*** 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;}

程序执行到这里我们还没有拿到我们的值,下面这个方法的第一行代码便执行我们将要覆写的initialValue()方法,它会返回我们所需要的变量副本(可以看看上面的例子,在initialValue方法中我new了一个ArrayList对象并返回)。拿到我们的变量副本之后,如果当前线程的threadLocals变量不为空,则添加到其中,如果为空,需要创建一个ThreadLocalMap,也就是会执行setInitialValue方法中的createMap(t,value)方法。

  /*** 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);elsecreateMap(t, value);return value;}

下面方法的作用是把new一个ThreadLocalMap,并给当前线程也就是Thread对象的threadLocals变量赋值。这样之后,我们的变量副本便保存在了ThreadLocalMap中了,同时当前线程Thread的对象的threadLocals变量又保存了这个map,所以,我们的变量副本就和当前线程挂钩了。

/*** 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* @param map the map to store.*/void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

下面的是ThreadLocalMap静态内部类的构造方法。我们可以看到key为ThreadLocal类型,value是我们的变量副本。

/*** Construct a new map initially containing (firstKey, firstValue).* ThreadLocalMaps are constructed lazily, so we only create* one when we have at least one entry to put in it.*/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);}

我们回到setInitialValue()方法,程序执行倒数第二行createMap方法后,接着就返回了value,这个value也就是我们覆写initialValue()所返回的值(也就是副本)。createMap()方法只是把我们的副本保存在一个map中,同时为当前线程的threadLocals变量赋值。

以上便是ThreadLocal类的主要原理,知道了上面的知识,对于public void set(T value)方法就很好理解了!

细节注意:

1、在TheadLocal类中定义了privatestatic AtomicInteger nextHashCode = newAtomicInteger();,它是静态的,也就是在ThreadLocal对象之间是共享的数据。

2、ThreadLocal有一个静态的内部类ThreadLocalMap,它定义了privateEntry[]table;为什么是一个数组呢?一个线程可能有多个ThreadLocal对象,它们都保存在这个数组中了。怎么做到的呢?从ThreadLocal的源码我们可以看到,每次调用set方法的时候,都会先看看当前线程的threadLocals属性时候为空。如果不为空,就直接把我们的变量副本加到这个数组中。

java.lang.ThreadLocal实现原理和源码分析相关推荐

  1. Nacos高级特性Raft算法以及原理和源码分析

    Nacos高级特性Raft算法以及原理和源码分析 对比springcloud-config配置中心 springcloud-config工作原理 Nacos的工作原理图 springcloud-con ...

  2. Spring源码分析【6】-ThreadLocal的使用和源码分析

    Spring代码使用到了ThreadLocal java.lang.ThreadLocal.set getMap java.lang.Thread.threadLocals定义 回到set 如果map ...

  3. 【项目一、xxx病虫害检测项目】1、SSD原理和源码分析

    目录 前言 一.SSD backbone 1.1.总体结构 1.2.修改vgg 1.3.额外添加层 1.4.需要注意的点 二.SSD head 2.1.检测头predictor 2.2.生成defau ...

  4. 高级JAVA - 动态代理的实现原理和源码分析

    在之前的一篇文章中 , 我们简单了解了一下代理模式(JAVA设计模式 - 代理模式) , 本篇我们来学习一下动态代理的实现原理 , 以及源码是怎样的 . JDK动态代理的主要实现步骤如下 : 1 . ...

  5. java集合框架02——ArrayList和源码分析

    上一章学习了Collection的架构,并阅读了部分源码,这一章开始,我们将对Collection的具体实现进行详细学习.首先学习List.而ArrayList又是List中最为常用的,因此本章先学习 ...

  6. RocketMq-dashboard:topic 5min trend 原理和源码分析(一)

    本文阅读基础:使用或了解过rocketMq:想了解"topic 5min trend"背后的原理:想了解监控模式如何实现. RocketMq的dashboard,有运维页面,驾驶舱 ...

  7. ConcurrentHashMap的实现原理和源码分析

    原文链接:http://www.jianshu.com/p/7f42ba895a64 前言 在Java1.5中,并发编程大师Doug Lea给我们带来了concurrent包,而该包中提供的Concu ...

  8. Tomcat原理和源码分析

    Tomcat是什么? 首先看下官网的解释说明(看不懂的可以翻译一下),从第一句Tomcat是Java Servlet,JavaServer页,Java表达式语言和Java的WebSocket技术的一个 ...

  9. ConcurrentLinkedQueue的实现原理和源码分析

    原文链接:http://www.jianshu.com/p/26d9745614dd 前言 我们要实现一个线程安全的队列有两种实现方式一种是使用阻塞算法,另一种是使用非阻塞算法.使用阻塞算法的队列可以 ...

最新文章

  1. python 导出mysql 视图_【Python基础】mysql数据库视图是什么
  2. Html中版权符号的字体问题
  3. 一篇文章,带你了解 “机器学习工程师” 必备技能图谱
  4. iOS开发 - UITextView输入时高度自适应
  5. MySQL This function has none of DETERMINISTIC, NO SQL...错误1418 的原因分析及解决方法
  6. K8S 部署 ingress-nginx (三) 启用 https
  7. 2010年浙江大学计算机及软件工程研究生机试真题
  8. Hybris PriceRow的存储定义
  9. lamaba中reduce方法将集合中的所有整数相加,并返回其总和
  10. 【windows环境——VSCode安装教程】
  11. C# Word 转PDF
  12. ios mysql注册登录界面_iOS学习2:创建属于自己的页面,自定义初始界面
  13. Docker系列(七)构建镜像
  14. DPDK分析——UIO
  15. 直方图规定化(匹配)
  16. python输入一个三位整数、求逆序数_编写程序,从键盘输入一个三位数,求出其逆序数并输出,例如输入123,输出321。编写程序,从键盘输入一个三位数...
  17. 代码review总结
  18. GitHub创建仓库
  19. linux大业内存,linux 内存占用过大分析
  20. 仿京东详情页商品图片查看

热门文章

  1. 股票自动交易python下单接口_用 Python 写了个简单的股票量化交易框架
  2. PTA 基础编程题目集 7-33 有理数加法 C语言
  3. 关于素数常用结论--威尔逊定理、欧拉定理、费马小定理、米勒罗宾算法
  4. 语音识别系统wav2letter++简介
  5. 闭包,函数式编程学习小记
  6. JavaScript 写几个简单的知识点
  7. 24 UsageEnvironment使用环境抽象基类——Live555源码阅读(三)UsageEnvironment
  8. 轻量级HTTP服务器Nginx
  9. 在中间层 .NET 应用程序中通过授权管理器使用基于角色的安全
  10. linux jna调用so动态库