ThreadLocal源码级别详解
ThreadLocal的作用
ThreadLocal作用是可以实现每个线程都有自己专属的本地变量。在实际的Web开发中,可以用该类来保存用户信息等,这样对于对于一个应用来说每个工作线程可以通过不同的用户信息处理业务逻辑,ThreadLocal另外一个用途就是可以用来减少方法的入参。
ThreadLocal的使用
下面是一个使用ThreadLocal的简单Demo:
public class DemoThreadLock {
private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> "hello, word!");
public static void main(String[] args) {
new Thread(new Worker(), "Thread1").start();
new Thread(new Worker(), "Thread2").start();
}
static class Worker implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "-初始值为:" + threadLocal.get());
threadLocal.set(Thread.currentThread().getName());
try {
//等地啊1s保证每个线程都设置完自己的值
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName() + "-最终值为:" + threadLocal.get());
}
}
}
上面代码最终的执行结果是:
Thread2-初始值为:hello, word!
Thread1-初始值为:hello, word!
Thread1-最终值为:Thread1
Thread2-最终值为:Thread2
在实现了Runnale接口的Worker类中,每个线程给threadLocal赋完值以后等待1s,这样能保证两个线程都是在赋完值以后再执行打印。从最终的结果可以看到两个线程打印结果不同。
ThreadLocal源码
创建对象
- 无参构造方法创建
- 使用ThreadLocal静态方法:
withInitial(Supplier<? extends S> supplier)
,参方法将会创建一个SuppliedThreadLocal
对象,这个对象是ThreadLocal的静态内部类,该类继承了类ThreadLocal,并重写了ThreadLocal
的空方法initialValue()
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> { private final Supplier<? extends T> supplier; SuppliedThreadLocal(Supplier<? extends T> supplier) { this.supplier = Objects.requireNonNull(supplier); } @Override protected T initialValue() { return supplier.get(); } }
方法get()
在Thread实例中ThreadLocalMap类型属性threadLocals并不是在创建Thread时创建的,而是当我们调用ThreadLocal的get()或set()方法时才会创建。
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) {
return t.threadLocals;
}
在第三行getMap(t)方法中,获取当前线程的ThreadLocalMap类型属性。由于在第一次调用该方法的时候,该属性还没有被创建所以为null,将会返回setInitialVaule()方法创建的对象。如果map不为null时,将会通过当前ThreadLocal对象做为key,在ThreadLocalMap中获取对应的Entry(该类继承了WeakReference构造该对象时会将key设置为弱引用,而vaule仍然未强引用,这也就是导致内存泄漏的原因),然后返回Entry包含的值。
接下来我们来看看方法setInitialValue(),在该方法中将会创建Thread的属性threadLocals
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);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
首先,通过initialValue()方法获取初始值(在前面介绍ThreadLocal构造时,我们知道在方法静态方法withInitial中会返回一个重写了该方法的类SuppliedThreadLocal),接着获取到当前线程中的ThreadLocalMap属性得到map,此时map仍为null,所以将会调用createMap方法给当前线程实例赋值一个新创建的ThreadLocalMap实例。
方法set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}
}
方法set()很简单,获取当前线程实例的属性ThreadLocalMap,如果该对象为null,那么就创建一个对象并赋值给Thread实例的属性,并带有初始值value.
ThreadLocal内存泄漏
首先我们来了解两个相关的概念
- 内存泄漏(Memory Leak):是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。 内存泄漏缺陷具有隐蔽性、积累性的特征,比其他内存非法访问错误更难检测。
- 弱引用:如果一个对象的引用是弱引用,那么在JVM进行垃圾回收时该对象将会被回收(除了弱引用还有强引用,软引用,虚引用)。
ThreadLocal中保存的值最终将会被包装成Entry(ThreadLocal中的静态类ThreadLocalMap中的静态类Entry),该类继承了WeakReference,构造该对象时会将key设置为弱引用,但是vault还是强引用。这样在JVM垃圾回收的时候,key就会被回收而value不会,这样就会存在key为null的Entry,如果不处理这些问题value将永远不会被回收。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
转载自:https://juejin.cn/post/7344571285848227852