ThreadLocal的使用与源码解析详解编程语言
对于ThreadLocal,那是众所周知的,因为在项目中它是常常被使用的,先简单说下平常使用的场景:
设置每个线程所需要的独享数据 设置内存中需要的全局变量(譬如拦截其中的用户信息)在ThreadLocal类的注释中,作者给的便是这样的定义,如图:
这段注释大体的意思是说(笔者英语极差):ThreadLocal这个类提供了thread-local变量,这些变量与普通变量不同,每个线程可以根据get或者set方法来操作自己独立初始化的变量副本。ThreadLocal 实例通常是类中的 private static 字段,是要将状态与线程相关联的。
那么上面说的两种使用场景其实都是基于这样的一个定义去做的,这里就第一种场景来提供一下代码实现:
public class SimpleDateFormatterDemo { public static ExecutorService executorService = Executors.newFixedThreadPool(10); public static void main(String[] args) { for (int i = 0; i i++) { int j = i; executorService.submit(new Runnable() { @Override public void run() { String date = new SimpleDateFormatterDemo().date(j); System.out.println(date); }); executorService.shutdown(); public String date(int seconds) { Date date = new Date(1000 * seconds); SimpleDateFormat dateFormat = SafeDateFormatter.dateFormatThreadLocal.get(); return dateFormat.format(date); static class SafeDateFormatter { public static ThreadLocal SimpleDateFormat dateFormatThreadLocal = new ThreadLocal SimpleDateFormat () { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); }二、ThreadLocal的设计
先不多说,直接上ThreadLocal的UML图,如下:
从上面的图中可以得出,ThreadLocal内部有 SuppliedThreadLocal 和 ThreadLocalMap这两个类,在 ThreadLocalMap 内部还有一个 Entry 内部类,这个类它是一个键值对(键:就是当前的类ThreadLocal,值是实际的成员变量,譬如上面那个示例代码中的SimpleDateFormat),Entry是基于 WeakReference 的,这样的设计也给ThreadLocal带来了一些相应的问题,我们后面再讨论这个。这里的 ThreadLocalMap 其实就是一个定制的 HashMap。
其实ThreadLocal是基于Thread类的,而在Threa中还有一个inheritableThreadLocals(另一个是threadLocals),在默认情况下,每个线程中的这两个变量都为null,只有在当前线程第一次调用ThreadLocal的 set 或 get 方法的时候才会创建他们,每个线程的本地变量并不是放在ThreadLocal实例中的,而是放在 threadLocals 中的,当线程调用get方法的时候,再从当前线程的threadLocals 变量中将其拿出来使用,当不需要的时候在调用remve方法,从当前线程的threadLocals中删除数据。在这里ThreadLocal的作者将Thread的threadLocals设计为map结构,其实是为了使每个线程可以关联多个ThreadLocal变量。
三、ThreadLocalMap源码在上面说了下ThreadLocalMap的代码实现,这里我们来看看ThreadLocalMap的源码,注:ThreadLocalMap中是采用线性探测法,也就是如果发生冲突,就会继续找下一个空位置,而不是用链表拉链。
成员变量
// 初始容量,必须是2次幂 private static final int INITIAL_CAPACITY = 16; // 存放数据的table,Entry类的定义在下面分析,table.length必须是2的冥 private Entry[] table; // 数组里面entrys的个数,可以用于判断table当前使用量是否超过负因子 private int size = 0; // 进行扩容的阈值,表使用量大于它的时候进行扩容 private int threshold; // Default to 0 // 定义为长度的2/3 private void setThreshold(int len) { threshold = len * 2 / 3; }
存储方式:Entry
/** * Entry继承WeakReference,并且用ThreadLocal作为key.如果key为null * (entry.get() == null)表示key不再被引用,表示ThreadLocal对象被回收 * 因此这时候value也可以从table从清除。 static class Entry extends WeakReference ThreadLocal ? { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal ? k, Object v) { super(k); value = v; }
前面说了这里的Entry是继承自WeakReference这个类,且在Entry的构造函数中,关于key的赋值方式是通过WeakReference去赋值的,而弱引用的特点是如果这个对象只被弱引用关联,那么这个对象就可以被回收,所以弱引用不会阻止GC。ThreadLocalMap的每个Entry都是一个对key的弱引用,同时每个Entry又包含一个对value的强引用,因此在正常的情况下,当线程终止,保存在ThreadLocal里的value会被垃圾回收,因为没有任何强引用。但是当这个线程一直不终止,那么key对应的value就不能被回收,因此就会产生这样的调用连:Thread—— ThreadLocalMap —— Entry(key为null)—— Value。因为value和Thread值阿健存在强引用关系,所以会导致value无法回收,以至于可能出现OOM。但是JDK已经帮我们想到了这一点,看源码:
private void resize() { Entry[] oldTab = table; int oldLen = oldTab.length; int newLen = oldLen * 2; Entry[] newTab = new Entry[newLen]; int count = 0; for (int j = 0; j oldLen; ++j) { Entry e = oldTab[j]; if (e != null) { ThreadLocal ? k = e.get(); if (k == null) { // 这里判断当前的key为null时,就会把value的值设为null,一次来帮助GC e.value = null; // Help the GC } else { int h = k.threadLocalHashCode (newLen - 1); while (newTab[h] != null) h = nextIndex(h, newLen); newTab[h] = e; count++; setThreshold(newLen); size = count; table = newTab; }四、set方法源码
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 将当前线程作为key,去查找对应的线程变量,找到了之后便进行设置 ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else // 第一次调用便创建当前线程对应的HashMap createMap(t, value); ThreadLocalMap getMap(Thread t) { return t.threadLocals; void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
这段代码很简单,不过在前面有说过,ThreadLocalMap是在第一次调用get()或者set()方法的时候才会进行初始化的。代码如下:
ThreadLocalMap(ThreadLocal ? firstKey, Object firstValue) { // 初始化 Entry table = new Entry[INITIAL_CAPACITY]; // 计算索引值 int i = firstKey.threadLocalHashCode (INITIAL_CAPACITY - 1); // 根据索引值进行设置 table[i] = new Entry(firstKey, firstValue); size = 1; // 设置阈值 setThreshold(INITIAL_CAPACITY); }
这计算索引值的步骤是比较重要的:firstKey.threadLocalHashCode (INITIAL_CAPACITY 1),这里firstKey.threadLocalHashCode是基于原子类AtomicInteger,先看具体代码:
private final int threadLocalHashCode = nextHashCode(); private static int nextHashCode() { return nextHashCode.getAndAdd(HASH_INCREMENT);
public final int getAndAdd(int delta) { return unsafe.getAndAddInt(this, valueOffset, delta); private static final int HASH_INCREMENT = 0x61c88647;
关于 (INITIAL_CAPACITY 1),通过AtomicInteger并与其相关的阈值获得hashCode,然后来进行取模,这里是以2次幂作为模数进行取模,用%代替代替2^n。
五、get方法源码
public T get() { // 获取当前线程 Thread t = Thread.currentThread(); // 获取当前线程的 threadLocals变量 ThreadLocalMap map = getMap(t); // 如果threadLocals不为null,则返回对应的本地变量的值 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; // threadLocals 为 null,则初始化当前线程的threadLocals变量 return setInitialValue(); private T setInitialValue() { // 初始化为 null T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 如果当前线程的 threadLocals 变量不为null if (map != null) map.set(this, value); else // 如果当前线程的 threadLocals 变量为null createMap(t, value); return value; protected T initialValue() { return null; }
本文是基于源码分析jdk1.8.0_131,本文主要从以下几点进行源码分析的:
简单的说了一下ThreadLocal的设计理念和基本使用 分析了ThreadLocal的源码实现与其实现的意义 ThreadLocalMap源码分析 set方法源码分析 get方法源码分析19403.html
cjava相关文章
- [项目源码]ERP进销存系统
- 好用的在线客服系统Go语言源码-GOFLY ( 开源代码+安装教程)
- 从React源码来学hooks是不是更香呢
- Juc并发编程11——深入源码:常用并发容器、阻塞队列使用与原理
- springboot使用拦截器_通用mapper源码
- 云海视频解析系统V4.3免授权版源码
- 【Linux 内核】实时调度类 ③ ( 实时调度类 rt_sched_class 源码 | 调度类 sched_class 源码 )
- 【Linux 内核 内存管理】Linux 内核内存布局 ④ ( ARM64 架构体系内存分布 | 内核启动源码 start_kernel | 内存初始化 mm_init | mem_init )
- kafka源码解析之十五客户端如何创建topic详解编程语言
- 集合之ArrayList的源码分析详解编程语言
- Django Rest Framework源码剖析(八)—–视图与路由详解编程语言
- [PHP] PHP源码常用代码中的宏定义详解编程语言
- TreeSet实现原理及源码分析详解编程语言
- 阿里云MQ-ONS消息队列使用说明文档和demo源码详解编程语言
- java多线程系列(九)—ArrayBlockingQueue源码分析详解编程语言
- [五]类加载机制双亲委派机制 底层代码实现原理 源码分析 java类加载双亲委派机制是如何实现的详解编程语言
- LRUMap源码分析详解编程语言
- 源码驱动:使用SQL Server实现程序可视化(源码与sqlserver)
- Redis构建选举机制深入研究其源码(redis选举源码)
- 编写一个含二级目录的源码(Asp+JavaScript)