ThreadLocal 到底是啥
ThreadLocal 到底是啥,我们基于java8来看看,它到底是个什么东西
ThreadLocal
以下为测试代码
测试代码中可以看到,threadlocal 初始化在最外层,各个线程数内操作设值跟获取都互不影响,即使是对同一个threadlocal进行操作。
源码
public class ThreadLocal<T> {
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的map
ThreadLocalMap map = getMap(t);
if (map != null) {
//map对应key为对应调用的threadLocal实例对象,value为本次设置的值
map.set(this, value);
} else {
createMap(t, value);
}
}
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();
}
ThreadLocalMap getMap(Thread t) {
//Thread中的属性ThreadLocal.ThreadLocalMap threadLocals = null;
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
}
ThreadLocal的get操作还是set操作,第一步都是先获取当前线程,设值到线程的ThreadLocal.ThreadLocalMap
中、获取值也是从线程的ThreadLocal.ThreadLocalMap
中获取。
Thread 没有任何保存当前数据的功能,但有对应的属性。我们想要保存一些当前线程才有的数据,并且线程件无法互用,于是有了ThreadLocal 的存在。Thread 只提供了一个属性ThreadLocal.ThreadLocalMap threadLocals
来给 ThreadLocal
进行操作。
ThreadLocalMap
我们来看下ThreadLocalMap到底是什么
/**
* ThreadLocal.ThreadLocalMap的Entry继承了WeakReference,是弱引用
**/
public class ThreadLocal<T> {
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
//同一个线程,每创建一次都会对 threadLocalHashCode 进行获取新值
private static int nextHashCode() {
return nextHashCode.getAndAdd(1640531527);
}
static class ThreadLocalMap {
private Entry[] table;
private static final int INITIAL_CAPACITY = 16;
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
//设值到tab中,如果key一致,则更换value
//否则则新增
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)
rehash();
}
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
}
}
对于 ThreadLocalMap
来说,就是将数据放置到Entry数组里,key为当前线程所set的thraedlocal对象
ThreadLocal.ThreadLocalMap.Entry
中的key是弱引用的,也就是当某个ThreadLocal对象不存在强引用时,就会被GC回收,这样设计是因为当当前线程还存活,但是已经不再执行引用,ThreadLocal.ThreadLocalMap.Entry
中的key如果是强引用会一直不被回收,我们的线程也存活着,就会导致内存泄漏,所以key被设计成弱引用。
但是value
是基于强引用的,所以当key被回收,但是value
还存在其他强引用时,就会出现内存的泄露情况,所以要养成用完 ThreadLocal
对象之后及时 remove
的习惯,remove
会将对应的 ThreadLocal
对象所在的entry直接清除引用。
value使用强引用的可能是,如果value为弱引用,这时候内存不够,value被删了,key还在,这时候会出现,对于entry应该成对存在的值,key存在,value却没了,可能会导致其他bug的产生。
内存泄露 (Memory Leak):就是申请了内存,但是没有释放,导致内存空间浪费,也就是占着茅坑不拉屎
ThreadLocal<String> threadLocal = new ThreadLocal<>();
try {
threadLocal.get();
} catch (Exception e) {
threadLocal.remove();
}
InheritableThreadLocal
子类线程获取主线程的信息,使用InheritableThreadLocal
,Inheritable就是可以继承的意思
对应测试用例
public static void main(String[] args) {
InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
ThreadLocal<String> threadLocal = new ThreadLocal<>();
inheritableThreadLocal.set("Inheritable hello");
threadLocal.set("hello");
new Thread(() -> {
System.out.println(String.format("子线程可继承值:%s", inheritableThreadLocal.get()));
System.out.println(String.format("子线程值:%s", threadLocal.get()));
new Thread(() -> {
System.out.println(String.format("孙子线程可继承值:%s", inheritableThreadLocal.get()));
System.out.println(String.format("孙子线程值:%s", threadLocal.get()));
}).start();
}).start();
}
结果
InheritableThreadLocal 的源码
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
通过debug,查看是如何实现获取父线程
当子线程调用inheritableThreadLocal.get()
时,可以清楚看到,获取map时,是执行InheritableThreadLocal所重写的getMap,返回当前线程的inheritableThreadLocals 属性。
跟threadLocal 一样,Thread也是有一个对应属性inheritableThreadLocals 来保存对应的父线程信息。
问题来了,当前线程的inheritableThreadLocals 属性是什么时候设值的,从我们的测试代码可以看出,我们就是创建了子线程,然后就执行inheritableThreadLocal.get()
,我们可以看下创建线程的方法有什么东西
public class Thread implements Runnable {
ThreadLocalMap inheritableThreadLocals = null;
//创建线程的方法
public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);}
private init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
//获取当前线程,即为即将创建的线程的父线程
//我创建你,我就是你爸爸
Thread parent = currentThread();
...
//查看父线程有没有inheritableThreadLocals,
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//有就保存到新建线程的inheritableThreadLocals,这样来实现,子线程、孙线程获取到父线程的可继承信息数据
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
}
总的来说
InheritableThreadLocal 跟threadLocal一样,起到的只有设值的作用,对应的属性保存到在Thread里。
转载自:https://juejin.cn/post/7234763992332976183