zl程序教程

您现在的位置是:首页 >  工具

当前栏目

ThreadLocal的使用与源码解析详解编程语言

源码编程语言 使用 详解 解析 ThreadLocal
2023-06-13 09:11:51 时间

对于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的使用与源码解析详解编程语言

从上面的图中可以得出,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