ThreadLocal概述
- ThreadLocal提供线程一个独立的局部变量,解决了变量并发访问的冲突问题。
- 解决了线程安全的问题
- ThreadLocal 对比给Thread上synchronized同步机制:
前者空间换时间、后者时间换空间
Threadlocal主题思维结构图
核心部分会单独拿出来画图、分析!
Threadlocal使用
@RequiresApi(api = Build.VERSION_CODES.O)
public class ThreadLocalTest {
static ThreadLocal<String> local = ThreadLocal.withInitial(() -> {
return "默认初始值";
});
static ThreadLocal local1 = new ThreadLocal();
public static void main(String[] args) {
System.out.print("local = " + local.get()+'\n');
local.set("this Local");
local1.set("this Local1111");
// local.remove();
System.out.print("Local1 = "+local1.get() + " set Local = " + local.get());
}
}
Log:
核心函数(对外 public/protected)
initialValue
当前线程初始副本变量值,一般由子类继承ThreadLocal重写,return一个默认值
get
:返回此线程局部变量在当前线程副本中的值
public T get() {
Thread t = Thread.currentThread();
//1.根据当前线程调用getMap()获取Thread内部的ThreadLocal.ThreadLocalMap
ThreadLocalMap map = getMap(t);
//2 map不为null,从map中获取当前ThreadLocal的存储实体对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// 3 map为null,调用setInitialValue
// 3.1 map不存在,此线程中没用维护的ThreadLocalMap对象
// 3.2 map存在,但是没有与ThreadLocal关联的ThreadLocalMap.Entry
return setInitialValue();
}
private T setInitialValue() {
//1. 获取初始化线程变量副本
// 子类重写 initialValue
T value = initialValue();
//2.根据当前线程调用getMap()获取Thread内部的ThreadLocal.ThreadLocalMap
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//2.1 存在存入map
if (map != null)
map.set(this, value);
// 2.2 不存在创建map,并存入
else
createMap(t, value);
return value;
}
set
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的TreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 调用ThreadLocalMap的内部方法set(set后面会ThreadLocalMap分析中讲解)
map.set(this, value);
// 不存在创建map,并存入
else
createMap(t, value);
}
// 创建与ThreadLocal关联的ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
remove
:移除当前前程的副本变量值
通过getMap获取当前线程的ThreadLoaclMap,不为null调用ThreadLoaclMap内部的remove方法
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
ThreadLocalMap
set
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// 这里是为了解决Hash冲突问题
for (Entry e = tab[i];//获取Entry对象
e != null;// 不为null
e = tab[i = nextIndex(i, len)]) {
//i = nextIndex(i, len) 得到一个key值的元素的位置
ThreadLocal<?> k = e.get();
// 如果是同一个对象,最新的value会覆盖掉旧value
if (k == key) {
e.value = value;
return;
}
// 如果key为null,则替换它的位置
if (k == null) {
// 替换掉旧值
replaceStaleEntry(key, value, i);
// prevIndex(int i, int len)在此方法中调用
return;
}
// 否则往后一个位置找,直到找到空的位置
}
// 上面的for循环主要作用:出现哈希冲突时,1:看是否是同一个对象或者是是否可替换,
// 2:否则往后移动一位,继续判断
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
remove
/**
* Remove the entry for 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)]) {
// 如果存在这个对象,删除
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
总结
Thread内部结构:
组成
- 每个Thread内部有一个Map(ThreadLocalMap)
- Map里存储(Key)ThreadLocal,和(value)线程变量副本
- Thread内部的map由ThreadLocal维护,由ThreadLocal负责向map 设置/获取线程变量值
- 形成线程隔离
Entry
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- 存储的key(ThreadLocal),为弱引用
- 设计弱引用将对象生命周期和生命周期解绑
问题----------->
-
设计好处?
节省内存的使用,Thread销毁,内部的map也随之销毁 -
和synchronized比较?
synchronized: 时间换空间
ThreadLocal:空间换时间 -
ThreadLocal内存泄漏的真实原因?
ThreadLocalMap的生命周期和Thread一样长,如果没有手动删除key,就会造成内存泄漏。在使用完ThreadLocal,调用remove删除Entry
-
为什么使用弱引用?
如果使用强引用,当ThreadLocal 对象的引用(强引用)被回收了,ThreadLocalMap本身依然还持有ThreadLocal的强引用,如果没有手动删除这个key ,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收, 可以认为这导致Entry内存泄漏。
在使用完ThreadLocal后忘记调用remove,使用弱引用多一层保障:弱引用的ThradLocal被回收时,对应的value在下一次ThrealLocal调用get、set、remove时,旧元素会被清除,从而避免内存泄漏
。 -
hashCode冲突?
// 是一个提供原子操作的Integer类,通过线程安全的方式操作加减
private static AtomicInteger nextHashCode =
new AtomicInteger();
// HASH_INCREMENT = 0x61c88647 (16进制)目的哈希码均匀的分布在2的n次方的entry数组里减少hash冲突
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
// 减少hash冲突的次数
key.threadLocalHashCode & (len - 1);