本文是在自己学习ThreadLocal过程中,记录下有意思的东西,以及自己的一些想法。
文章目录
ThreadLocal作用
在Thread类中声明了一个叫做threadLocals的变量,类型是ThreadLocal的静态内部类ThreadLocalMap 。
public class Thread implements Runnable {
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
从注释可以看出该类是和线程有关的,因为每个线程线程都会独立初始化该变量。 threadLocals 类型是ThreadLocalMap ,那么肯定是用键值对读写的,其中key值为ThreadLocal对象,而value值为我们要保存的对象,一个ThreadLocal对象对应一个Value值。因此threadLocals 作用主要是保存、读取线程变量。
ThreadLocal源码分析
public class ThreadLocal<T> {
//创建时生成的哈希值,用来在map中找到散列位置。
private final int threadLocalHashCode = nextHashCode();
//一个原子整数
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
//每创建一个ThreadLocal对象,对应的threadLocalHashCode值增加0x61c88647。
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
/**
* 赋值操作,将value存储到当前线程的threadLocals中
* 泛型T即为我们声明和保存的对象类型。
*/
public void set(T value) {
//首先获得当前的线程
Thread t = Thread.currentThread();
//获取当前线程的保存键值对的threadLocals变量,详见分析一。
ThreadLocalMap map = getMap(t);
if (map != null)
//ThreadLocalMap.set方法分析见下文。
map.set(this, value);
else
//若该map不存在,则初始化该线程的ThreadLocalMap,详见分析二 。
createMap(t, value);
}
//以该ThreadLocal对象为key值,读取当前线程中对应的value值。
public T get() {
//获得当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
//调用ThreadLocalMap.get方法获得,ThreadLocalMap中保存键值对的对象Entry,分析见下文。
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T) e.value;
return result;
}
}
//若没有对应的value值,则返回初始值,见分析三
return setInitialValue();
}
//从当前线程的threadLocals中,移除以该TheadLocal对象为key值的键值对
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//调用ThreadLocalMap.remove方法,分析见下文。
m.remove(this);
}
//分析一,获得当前线程的threadLocals变量
ThreadLocalMap getMap(Thread t) {
//直接返回该线程的threadLocals变量
return t.threadLocals;
}
//分析二,初始化当前线程的threadLocals变量
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
/**
*分析三
*get操作没得到value值时,返回默认的value值
*同时将该键值对加入到map中
*/
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;
}
//默认初始化值为null,可以通过继承覆写该方法。
protected T initialValue() {
return null;
}
//保存键值对的map
static class ThreadLocalMap {}
}
set、get、remove操作都是先获得当前线程的ThreadLocalMap变量 ,然后以该ThreadLocal对象(即自身)为Key值,操作ThreadLocalMap变量。
因此声明一个static ThreadLocal对象后,在不同的线程调用会获得不同的值,使用时注意所在的线程。
ThreadLocal.ThreadLocalMap源码分析
static class ThreadLocalMap {
//该Map通过Entry数组来存储数据
private Entry[] table;
//数组中Entry的个数
private int size = 0;
//需要扩容的阈值
private int threshold
//Entry包括了一个Key值,一个value指,其中key值是一个弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
//key是一个弱引用
super(k);
value = v;
}
}
//把该数组当成了一个环形
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
//构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//数组初始化大小为16
table = new Entry[INITIAL_CAPACITY];
//通过ThreadLocal.threadLocalHashCode值,计算散列位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
//数组长度的2/3当做阈值
setThreshold(INITIAL_CAPACITY);
}
//通过key获得value值
private Entry getEntry(ThreadLocal<?> key) {
//通过threadLocalHashCode计算散列位置
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
//发生了冲突,从i开始向后扫描
return getEntryAfterMiss(key, i, e);
}
//发生了冲突,从i位置向后扫描
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e;
if (k == null)
//弱引用对应的对象不存在了,清除该对象
expungeStaleEntry(i);
else
//向后查找
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
//插入键值对
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 计算key值的散列位置
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//已存在的key值,覆盖旧的value值
if (k == key) {
e.value = value;
return;
}
//因为key值是个弱引用,引用已经不存在了,需要进行清除
if (k == null) {
//清理并插入新的键值对
replaceStaleEntry(key, value, i);
return;
}
}
//插入键值对
tab[i] = new Entry(key, value);
int sz = ++size;
//判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//首先清除所有的失效的弱引用,若还是不够,进行扩容。容量*2,重新计算散列的位置,
rehash();
}
//根据key值移除键值对
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)]) {
//根据key值,找到数组中对应的位置
if (e.get() == key) {
e.clear();
//删除tab[i]的对象
expungeStaleEntry(i);
return;
}
}
}
//清理失效的Entry
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//清理即将对象置为null
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
//重新计算相邻的entry的位置
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
//h为新的位置
tab[h] = e;
}
}
}
//下一个空槽的位置
return i;
}
}
key值使用弱引用
这里为什么key值为什么要使用弱引用,可以看下ThreadLocal的内存泄露问题。使用弱引用主要是为了解决内存泄漏的问题,如果ThreadLocal变量不在了,那么该引用key值也就没必要存在了,再根据key值将Entry删掉。为了避免两个生命周期不一样的对象,在引用时发生内存泄漏的问题。
数组长度为2^n
说下为什么Entry[]长度一定要是2n,HashMap、ArrayDeque 数组长度也是2n,原理是一样的。
x %2n = x &( 2n -1) 一个数x对2n求余时,可以转成 x &( 2n -1) ,位运算速度肯定是比求模运算快的。
想要把%运算转成&运算,只有当 X%2n才能实现,所以数组的长度必须是2n。
想要知道为什么可以看看这篇文章计算一个数与2的n次方取模。
get、set、remove操作
get、set、remove操作发现null key值时,就会将该Entry置空,并重新hash连续的Entry。
但如果你的ThreadLocal已经不在了,但你没有调用get、set、remove操作,还是会出现内存泄漏的问题,所以在不使用某个ThreadLocal时,主动调用remove的方法,来避免该问题。
关于threadLocalHashCode
这个threadLocalHashCode 十分有意思。
用该threadLocalHashCode % 2n 时,可以均匀的分配在Entry数组中,当有多个ThreadLocal加入时,减少冲突发生的概率,同时配合链地址法来解决hash表的冲突问题。那么为什么这个神秘数字可以均匀的分配在2^n数组上,看看这个文章ThreadLocal 和神奇数字 0x61c88647,原因是和黄金比例有关。
InheritableThreadLocal父线程向子线程传递变量
InheritableThreadLocal的使用如下
private static InheritableThreadLocal<String> mInheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String args[]) {
mInheritableThreadLocal.set("主线程赋值");
new Thread(new Runnable() {
@Override
public void run() {
//获得字符"主线程赋值"
String s = mInheritableThreadLocal.get();
}
}).start();
}
现在分析下原因,首先看下Thread的构造函数
//声明inheritableThreadLocals
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null
//使用new Thread时的构造函数
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
//parent是当前调用所在的线程
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup();
}
g.addUnstarted();
//复制父线程的变量
this.group = g;
this.target = target;
this.priority = parent.getPriority();
this.daemon = parent.isDaemon();
setName(name);
init2(parent);
}
private void init2(Thread parent) {
...
//如果父线程的inheritableThreadLocals不为空,则复制父线程的inheritableThreadLocals
if (parent.inheritableThreadLocals != null) {
//调用createInheritedMap方法,将父线程的inheritableThreadLocals复制给子线程的inheritableThreadLocals
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(
parent.inheritableThreadLocals);
}
}
子线程在创建时候,如果父线程的inheritableThreadLocals 存在,则会复制该变量给子线程的inheritableThreadLocals 。
看下InheritableThreadLocal相关代码
//继承自ThreadLocal
public class createInheritedMap<T> extends ThreadLocal<T> {
//对parentValue进行处理,目前无修改。若有需要可以覆写该方法进行处理
protected T childValue(T parentValue) {
return parentValue;
}
//覆写了getMap,返回的是inheritableThreadLocals而不是原来的threadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//覆写了createMap,为inheritableThreadLocals变量进行初始化
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
InheritableThreadLocal的代码很少,调用InheritableThreadLocal.set方法时候,会为初始化当前线程初的inheritableThreadLocals 。同样调用InheritableThreadLocal.get方法时,会从当前线程的inheritableThreadLocals 查找变量。
接下来看下ThreadLocal.createInheritedMap方法
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
//调用了ThreadLocalMap的构造方法,parentMap即父线程的inheritableThreadLocals变量。
return new ThreadLocalMap(parentMap);
}
static class ThreadLocalMap {
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
//初始化子线程的table
table = new Entry[len];
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
//复制父线程中key值有效的键值对
if (key != null) {
//调用createInheritedMap覆写方法childValue,此时返回e.value。
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);
//插入Entry
table[h] = c;
size++;
}
}
}
}
}
该方法复制了父线程的inheritableThreadLocals中有效的键值对给子线程,在子线程也有一个副本。
因此当父线程调用inheritableThreadLocals.set()后,子线程通过inheritableThreadLocals.get()可以获父线程中声明的变量。