此类提供线程局部变量。这些变量与普通变量不同,每个访问一个线程(通过其getset方法)的线程 都有其自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。

以射击游戏举例,游戏开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为100、0、10.设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?
如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?
如果共享,那么线程之间的并发修改会导致数据不准确.
能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal

一、类定义

public class ThreadLocal<T> {}

…用来限制Class中的参数类型,确保Class中参数的一致性

二、实例变量和相关方法

//用于ThreadLocalMap
private final int threadLocalHashCode = nextHashCode();//下一个hash code,从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();//hash增量
private static final int HASH_INCREMENT = 0x61c88647;//在获取下一个hash code
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}

三、内部类

内部类:ThreadLocalMap

ThreadLocalMap负责存储ThreadLocal及其变量,即ThreadLocal对象本身作为键,ThreadLocal存储的变量作为值。每个Thread对象在声明一个ThreadLocal后会持有一个ThreadLocalMap的引用,来实现ThreadLocal的功能。

ThreadLocalMap持有一个内部类Entry,类似于HashMap.Node类,负责保存键值对。

static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}

Entry继承了WeakReference类,使Entry的键为弱引用。

看到这里很多人或许有这样一个疑问:为什么Entry要继承WeakReference?
既然将ThreadLocal声明为弱引用,那么自然会联想到和GC有关。

如果不声明为弱引用,以最上面Test类的代码为例,当我们将上述ThreadLocal类型的静态变量tl设置为null时,Thread对象成员变量threadLocals依然保留有一个ThreadLocalMap,该Map中持有保存该ThreadLocalEntry,在这个线程运行期间无法GC,从而引发内存泄漏。所以,Entry的键要声明为弱引用。

四、主要方法

1.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();
}

首先是获取当前运行线程对象,然后根据该线程对象找到对应的ThreadLocalMap

  • 如果找到了该线程对应的ThreadLocalMap,则通过当前ThreadLocal对象作为键查找Map中对应的Entry(键值对)对象
  • 如果查找结果不为null,则返回Entry对象的value。否则调用setInitialValue方法将当前ThreadLocal对象(this)和变量作为键值对存入ThreadLocalMap并返回变量。

2.initialValue()

为变量设置初始值,该方法的默认实现是:

protected T initialValue() {return null;
}

如果想要为该变量设置一个初始值,只需重写该方法即可,例如:

@Override
protected String initialValue() {return "hello world";
}

3.set(T value)

get方法类似,set方法首先会获取当前运行的Thread对象并通过该对象获取对应的ThreadLocalMap,如果map为空,则为当前Thread对象新建一个ThreadLocalMap,否则直接将value放入map中。

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}

4、remove()

同样,获取当前Thread对应的ThreadLocalMap,然后调用ThreadLocalMapremove方法移除ThreadLocal对象,无需通过弱引用机制对该ThreadLocal对象进行GC。

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

五、总结

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?

ThreadLocal类中设置了一个Map,存储每一个线程的变量的副本。

ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。

Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

Threadlocal底层是通过threadlocalMap进行存储键值 每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。
ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

JDK源码解析之 java.lang.ThreadLocal相关推荐

  1. JDK源码解析之 java.lang.Thread

    位于java.lang包下的Thread类是非常重要的线程类,它实现了Runnable接口,今天我们来学习一下Thread类,在学习Thread类之前,先介绍与线程相关知识:线程的几种状态.上下文切换 ...

  2. JDK源码解析之 java.lang.Exception

    异常.是所有异常的基类,用于标识一般的程序运行问题.这些问题通常描述一些会被应用程序捕获的反常情况. 一.源码部分 //继承了java.lang.Throwable public class Exce ...

  3. JDK源码解析之 java.lang.Error

    java.lang.Error 错误.是所有错误的基类,用于标识严重的程序运行问题.这些问题通常描述一些不应被应用程序捕获的反常情况. 一.源码部分 //继承了java.lang.Throwable ...

  4. JDK源码解析之 java.lang.Integer

    teger 基本数据类型int 的包装类 Integer 类型的对象包含一个 int 类型的字段 一.类定义 public final class Integer extends Number imp ...

  5. JDK源码解析之 Java.lang.Object

    Object类是Java中其他所有类的祖先,没有Object类Java面向对象无从谈起.作为其他所有类的基类,Object具有哪些属性和行为,是Java语言设计背后的思维体现. Object类位于ja ...

  6. JDK源码解析之 Java.lang.Compiler

    Compiler类提供支持Java到本机代码编译器和相关服务.在设计上,它作为一个占位符在JIT编译器实现. 一.源码部分 public final class Compiler {private C ...

  7. JDK源码解析之 Java.lang.Byte

    byte,即字节,由8位的二进制组成.在Java中,byte类型的数据是8位带符号的二进制数,以二进制补码表示的整数 取值范围:默认值为0,最小值为-128(-27);最大值是127(27-1) By ...

  8. JDK源码解析之 Java.lang.String

    String 类代表字符串.Java 程序中的所有字符串字面值(如 "abc" )都作为此类的实例实现. 字符串是常量:它们的值在创建之后不能更改.字符串缓冲区支持可变的字符串.因 ...

  9. JDK源码解析之 Java.lang.Package

    如果我们在Class对象上调用getPackage方法,就可以得到描述该类所在包的Package对象(Package类是在java.lang中定义的).我们也可以用包名通过调用静态方法getPacka ...

最新文章

  1. 第八周项目一-数组做数据成员(3)
  2. apache2 的https配置和代理https后端nodejs配置
  3. mysql数据库的触发器_Mysql数据库触发器
  4. (Java多线程)线程状态
  5. 05.内存管理.md
  6. centos php 开启libgdgd_CentOS6.6下yum安装PHP的gd库失败?-问答-阿里云开发者社区-阿里云...
  7. 构建忽略测试_分类测试以减少构建时间
  8. 计算机公共基础知识论文,计算机等级考试二级公共基础知识汇总.doc
  9. 【报告分享】2020产业互联网发展报告:新范式.pdf(附下载链接)
  10. 安卓手机如何防盗_iphone手机换成安卓手机后如何转移便签备忘录数据?
  11. Openlayers layer 切换底图
  12. Go gorilla websocket 小试牛刀
  13. Arp病毒专杀工具下载及其防治解决方案
  14. 将ArcMap中的符号样式导出的供ArcPad使用
  15. 基于QT实现简单的音乐播放器
  16. 硬件编解码与软件编解码的区别
  17. 2021爱分析·产业数字化峰会圆满落幕:加快推动产业数字化,构建产业共生生态
  18. 史上最最最没用程序——自写平衡化学方程式
  19. 安全扫描工具 AppScan
  20. 第3周学习:ResNet+ResNeXt

热门文章

  1. c语言中规定的标准文件,标准C语言
  2. pipelines mysql_Scrapy爬取豆瓣图书数据并写入MySQL
  3. Qt关于Tcp通信步骤的总结
  4. 在matlab中使用spm8,在matlab中同时使用spm2,spm5,spm8
  5. 合并两个有序数组 java_合并两个有序的数组
  6. flv 自动播放 html autostart=true,《网页制作之FLV视频播放代码的编写.doc
  7. 打docker镜像_从安全到镜像流水线,Docker 最佳实践与反模式一览
  8. boot lvm 分区_Linux如何在线对逻辑分区扩容
  9. python位运算符_详细介绍Python语言中的按位运算符
  10. 某一个接口403 其他接口可以调通_Neo的务实外设指南 篇三十六:一个就够,65W快充+C口混插+最多6个设备 - 飞利浦65W摩天轮插座_插座...