ThreadLocal是什么?
ThreadLocal是线程本地变量,是一种存储在线程Thead上的ThreadLocalMap结构的变量,通常用于同一个线程内变量传递,或者变量共享。
注意:子线程内要获取父线程set值用 InheritableThreadLocal
使用示例
ThreadLocal<String> threadLocal = new ThreadLocal<>();
threadLocal.set("ThreadLocal");
String localValue=threadLocal.get();
threadLocal.remove();
ThreadLocal原理及源码解读
GET原理
get流程:通过Thread.currentThread()获取线程实体Thread,线程变量数据就保存在线程Thread上,是一个ThreadLocalMap(维护一个Entry[] table 数组)的属性变量threadLocals,如果map为空,则设置值为初始值null;如果map不为空,则通过当前线程哈希值计算的存储位置( key.threadLocalHashCode & (table.length - 1))来获取存储的Entry结构(是个保存object的弱引用对象)。
来观赏下源码~~
//返回此线程局部变量的当前线程副本中的值。 如果该变量对于当前线程没有值,则首先将其初始化为调用initialValue方法返回的值。
public T get() {
Thread t = Thread.currentThread();
//获取线程Thread的属性变量
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();
}
//获取与 ThreadLocal 关联的映射。 在 InheritableThreadLocal 中重写。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
SET原理
set流程:通过Thread.currentThread()获取线程实体Thread,获取线程Thread的属性变量threadLocals(ThreadLocalMap结构),首次为空时,threadLocals创建ThreadLocalMap实例(维护一个Entry[] table数组,初始容量为16,达到加载因子2/3是2倍扩容 );不为空时,通过线程变量的哈希值对数组长度与计算得到存储位置,并依据此获取数组的指定位置的Entry值,如果是当前线程的则返回覆盖,否则新增,最后判断是否清楚多余曹位及扩容;
//将此线程局部变量的当前线程副本设置为指定值。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//首次创建Map数据
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
划重点
所以线程变量本质是存储在线程java.lang.Thread上的属性变量,通过ThreadLocal类维护ThreadLocalMap结构的属性值:
//JDK1.8
public class Thread implements Runnable {
//属性省略
....
//与此线程有关的 ThreadLocal 值。该映射由 ThreadLocal 类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
//与此线程有关的 InheritableThreadLocal 值。此映射由 InheritableThreadLocal 类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//Thread ID
private long tid;
//返回此线程的标识符。 线程 ID 是在创建该线程时生成的一个长正数。
// 线程 ID 是唯一的,并且在其生命周期内保持不变。 当一个线程终止时,这个线程 ID 可能会被重用
public long getId() {
return tid;
}
....
}
看到这里想必好奇ThreadLocalMap到底是个什么结构
ThreadLocalMap 是一种定制的哈希映射,仅适用于维护线程本地值。 不会在 ThreadLocal 类之外导出任何操作。 该类是包私有的,以允许在类 Thread 中声明字段。 为了帮助处理非常大和长期存在的用法,哈希表条目使用 WeakReferences 作为键。 但是,由于不使用引用队列,因此只有在表开始耗尽空间时才能保证删除陈旧条目。
这里列出几个重要的方法和属性:
//JDK1.8
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** 与此 ThreadLocal 关联的值 */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//存储数组,初始为16,长度必须为2的幂
private Entry[] table;
private int threshold;//扩容阈值,初始为长度的2/3
//初始化
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
//计算存储位置
//这里重点说下threadLocalHashCode是一个自定义的乘法哈希值
//当数组长度是2的幂减1的时候,取余等于按位与操作
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
//获取与key关联的value。
// 此方法本身仅处理快速路径:直接点击现有密钥。 否则它会到 getEntryAfterMiss。
//这旨在最大限度地提高直接命中的性能,部分原因是使该方法易于内联。
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
//其实关键方法还是在set里面
private void set(ThreadLocal<?> key, Object value) {
// 我们不像使用 get() 那样使用快速路径,因为它
// 至少与使用 set() 创建新条目一样常见
// 它用于替换现有条目,在这种情况下,使用快速
// 路径会经常失败。
Entry[] tab = table;
int len = tab.length;
//计算存储位置
int i = key.threadLocalHashCode & (len-1);
//拿出值后如果不是当前key,则指针i递增1,继续寻找
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
//当发生哈希冲突时,且当前位置线程变量过期
if (k == null) {
//清理过时槽,与过时槽交换以维护哈希表顺序
replaceStaleEntry(key, value, i);
return;
}
}
//找不到则新增
tab[i] = new Entry(key, value);
int sz = ++size;
//清理过期值,判断是否需要扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//扩容:当达到数组长度2/3是,清理旧条目然后2倍扩容
//新存储位置计算是重新哈希,如果冲突则位置+1直到找到空槽位
rehash();
}
}
ThreadLocal使用场景
1) 对象跨层传递
定义一个通用的线程变量类,不同的方法和业务类,无需参数传递即可使用,代码太长,在另外一篇文章内贴吧,这里给一个hystrix框架的典型应用
package com.netflix.hystrix;
public class Hystrix {
//定义一个连表结构的静态线程变量,保存当前的命令
private static ThreadLocal<LinkedList<HystrixCommandKey>> currentCommand = new ThreadLocal<LinkedList<HystrixCommandKey>>() {
@Override
protected LinkedList<HystrixCommandKey> initialValue() {
return new LinkedList<HystrixCommandKey>();
}
};
//当前线程获取其保存的值
public static HystrixCommandKey getCurrentThreadExecutingCommand() {
if (currentCommand == null) { return null;}
return currentCommand.get().peek();
}
}
2)线程间数据隔离
看一个框架内的使用示例
package com.sun.jersey.core.header;
//HTTP 指定日期格式的帮助程序类
public class HttpDateFormat {
....
//通过一个静态ThreadLocal变量,做到线程隔离,适用于http请求公用操作类。
private static ThreadLocal<List<SimpleDateFormat>> dateFormats = new ThreadLocal<List<SimpleDateFormat>>() {
@Override
protected synchronized List<SimpleDateFormat> initialValue() {
//初始化要用的日期格式
return createDateFormats();
}
};
....
}
3)进行事务操作时,用于存储线程事务信息
4)数据连接,Session会话管理
。。。太多了,代码就不贴了
ThreadLocal小结
- ThreadLoacl是操作线程变量的操作辅助类,线程变量是存储在线程Thread上的ThreadLocalMap结构的属性变量。
- 用完一定要记得清除remove,否则容易造成内存泄露
- 父线程定义ThreadLocal子线程是获取不到,如果父给子线程传递对象用InheritableThreadLocal