1、前言
Q:什么是 ThreadLocalhtml
A:ThreadLocal 只是一个引用名称, 其类型为 ThreadLocalMap;ThreadLocal 故名思议线程本地,字面上推断出它的做用就是 ThreadLocal 为每一个线程提供了一种将可变数据经过变为私有副本从而实现线程封闭的功能。java
Q:若是叫你本身试着实现一个 ThreadLocal,你该如何去写代码?算法
A:由于咱们要实现线程私有,因此咱们的 key 得标记是哪一个线程,value 就是线程本身须要保存的数据咯;综上,因此咱们须要实现一个线程安全的 Map,key、value 如上所述。segmentfault
2、源码阅读
2.1 结构总览
继承结构
数组
- 每一个线程 Thread 内部都有个 ThreadLocalMap
- ThreadLocalMap 集合内部使用 Entry 来存储键值对,其中 key 为ThreadLocal(弱引用),value 为实际存储的值。
2.2 ThreadLocal.set(T val)
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocal.ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue); }
逻辑以下:安全
- 获取当前线程实例
- 根据当前线程,获取其中的 ThreadLocalMap 对象 threadLocals map
- 若 map 存在,则将 threadLocal 做为 key 并保存 value
- 假如 map 为 null,则建立新 map 而后塞值保存
2.3 ThreadLocal.remove(T val)
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); } private void remove(ThreadLocal<?> key) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { if (e.get() == key) { e.clear(); expungeStaleEntry(i); return; } } }
- 获取当前线程的 ThreadLocalMap threadLocals
- 而后传入 ThreadLocal 对象做为 key,而后遍历 Entry 数组,找到 key 而后进行 remove 操做
2.4 ThreadLocal.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(); } private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } protected T initialValue() { return null; }
- 获取当前线程实例
- 根据当前线程,获取其中的 ThreadLocalMap 对象 threadLocals map
- 若 map 存在,将 threadLocal 做 为key 得到 Entry 对象
- 若是 Entry 对象不为空,返回其中的 Value
- 若是 map 为空或者 map 为不空且 entry 为空则设置初始值 null 而后返回
3、ThreadLocalMap 介绍
2.1 ThreadLocalMap 的成员变量、方法
ThreadLocalMap 总览 this
重要的成员变量url
static class ThreadLocalMap { static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } } /** * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; /** * The number of entries in the table. */ private int size = 0; /** * The next size value at which to resize. */ private int threshold; // Default to 0 /** * Set the resize threshold to maintain at worst a 2/3 load factor. */ private void setThreshold(int len) { threshold = len * 2 / 3; } /** * Increment i modulo len. */ private static int nextIndex(int i, int len) { return ((i + 1 < len) ? i + 1 : 0); } /** * Decrement i modulo len. */ private static int prevIndex(int i, int len) { return ((i - 1 >= 0) ? i - 1 : len - 1); } ... }
由上可得:.net
- ThreadLocalMap 并无继承 Map
- ThreadLocalMap 维护了一个 Entry 数组,而且要求数组的大小必须为 2 的幂,同时记录表里面 entry 的个数以及下一次须要扩容的阈值。
- Entry 用来保存 K-V,Entry 中 key 只能是 ThreadLocal 对象,且 Entry 继承自 WeakReference(弱引用,生命周期只能存活到下次 GC 前),但只有 Key 是弱引用类型,Value 并不是弱引用类型。
- ThreadLocalMap 有两个方法用于获得上一个/下一个索引,这是由于 ThreadLocalMap 使用线性探测法来解决散列冲突,因此实际上 Entry[] 数组在程序逻辑上是做为一个环形存在的。
构造方法线程
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; // 用firstKey的threadLocalHashCode与初始大小16-1取模获得数组下标 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 初始化该节点 table[i] = new Entry(firstKey, firstValue); size = 1; // 设定扩容阈值 setThreshold(INITIAL_CAPACITY); } /** * Construct a new map including all Inheritable ThreadLocals * from given parent map. Called only by createInheritedMap. * * @param parentMap the map associated with parent thread. */ private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len]; for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal<Object> key = (ThreadLocal<Object>) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }
4、文末存疑及总结
Q:ThreadLocalMap 如何解决哈希冲突?
A:ThreadLocalMap 根据初始 key 的 hashcode 肯定元素在 table 数组中的位置,若是发现这个位置上已经有其它 key 值的元素被占用时,会利用固定的算法寻找必定步长的下个位置,直至找到可以存放的位置,这就是所谓的线性探测。
Q:ThreadLocalMap 中 Entry 类的 key 为何是弱引用?
A:方便 JVM 进行垃圾回收;ThreadLocalMap 存储的数据的格式是Entry<ThreadLocal, T>
,且 Java 中而引用传递的是对象的副本,那么若是使用强引用,当原来 key 对象失效的时候,JVM 不会回收 map 里面的 ThreadLocal,而若是 key 为弱引用,那么 GC 在扫描到该对象时,不管内存充足与否,都会回收该对象的内存。
Q:key 设置为弱引用有什么问题?
A:从共享资源的角度可看出 ThreadLocal 是用空间换取时间,好比 Synchronize 从某种意义上来讲,则是利用时间换取了空间,它对全部线程只设置一份变量,而若是使用 ThreadLocal,那么每一个线程均可以存储多个 ThreadLocal,可是当线程执行结束之后,ThreadLocal 对象会被回收,但这些线程内部的 ThreadLocalMap 中 Entry 对象的 value 还不会被回收,那么这将很容易致使堆内存溢出。
针对这一问题,解决方案有:手动调用 ThreadLcoalMap 中的 get()/set()/remove()
方法实现 key 的删除,这些方法在清理为 null 的 key 时,会顺带清理其 value
https://blog.csdn.net/weixin_42388901/article/details/96491793
csdn
- 245赞:http://www.javashuo.com/article/p-fpaejesi-dp.html
- 应用:http://www.javashuo.com/article/p-pcbfrgas-bd.html
简书
- 132:https://www.baidu.com/link?url=WMEW5syXgxpTGMQ-yXmOH9cl6J5u8Q7O68AOPD9VRSS3J5MZ38utrXdkLHEhlaZf&wd=&eqid=a7b482590004fa04000000065eea2fae
- 163:https://www.baidu.com/link?url=M71TCsOCGRHqX2odYPyWjHEfOmPV3RHFOUAXeP8Fi0L9UNtY_uKATRErNhG-oeD_&wd=&eqid=a7b482590004fa04000000065eea2fae
博客园:
- 37 推荐:http://www.javashuo.com/article/p-mksbvkvj-dm.html
- 121:http://www.javashuo.com/article/p-oiitcxzh-gv.html
其余:
- 清晰:https://www.cnblogs.com/jayhou/p/9811445.html#_caption_0
- 不少文章那个:https://www.pdai.tech/md/java/thread/java-thread-x-threadlocal.html
思否: