ThreadLocal内部结构
ThreadLocal与Thread的关系
在Thread类里面维护了一个字段ThreadLocalMap,但是对于这个字段的操作方法,由ThreadLocal进行声明。
两个线程使用同一个threadLocal,为什么没有线程安全问题?
public class ThreadLocalDemo {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void print(String str) {
System.out.println(str + " :" + threadLocal.get());
threadLocal.remove();
}
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
threadLocal.set("local_A");
print("A");
System.out.println("after remove : " + threadLocal.get());
},"A").start();
Thread.sleep(1000);
new Thread(() -> {
threadLocal.set("local_B");
print("B");
System.out.println("after remove : " + threadLocal.get());
},"B").start();
}
}
针对以上代码,可能误以为存在线程安全问题,ThreadLocal使用了什么神奇的方式解决了该问题,但其实根本就不存在线程安全问题,看threadLocal.set();和threadLocal.get();源码可知
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);
}
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set方法,其实是获取当前线程的t.threadLocals字段,该字段就是真正用来存储数据的,而不是ThreadLocal threadLocal = new ThreadLocal<>();存储数据的。自己new的ThreadLocal,仅仅用来当做key存到线程的threadLocals。
因此这里虽然两个线程都在使用自己new的threadLocal,可是这个对象仅仅用来当做key存放到各自线程的threadLocals字段中。真正的容器是各自线程的threadLocals字段。所以此处是每个线程都有一个存放数据的容器threadLocals字段,两个容器都是用了同一个对象当做key。
ThreadLocal的remove方法,会将Entry的key和value都置空。如果不手动调用remove方法。Entry中的key是弱引用。在下次gc时,会回收弱引用的对象。但是value是强引用。因此可能存在entry中key是null,value有值的情况。如果value没有其他外部引用的话,value将会随着线程生命周期结束,而结束。
ThreadLocal使用场景,比如数据库事务,用户session
InheritableThreadLocal 可继承的ThreadLocal
InheritableThreadLocal是ThreadLocal的子类,该类没有扩充新字段
只是重写了getMap和createMap方法。
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
创建线程的时候,会判断主线程的inheritableThreadLocals是否为null,如果不是null,就把主线程中.inheritableThreadLocals的内容复制到子线程。用来传递到子线程
但如果在线程池中,由于线程是复用的,因此无法进行传递
线程池中不同线程值传递的解决方案:阿里云开源的TransmittableThreadLocal。
ThreadLocal可能导致内存泄露的条件
1、没有手动删除数据
2、线程一直运行,没有结束,比如线程池的线程
3、没有再次调用set/get/remove等方法,
3的解释因为调用这些方法的时候,会判断key为null的Entry,如果key为空,那么value应该就需要被清理了。因此会把对应的Entry清空。不仅仅当前位置的,会依次遍历下个位置,只要key为null的Entry就会被清空。
删除无效数据的方法expungeStaleEntry被调用的时机:
1、set的时候如果map满了,扩容的时候会调用。
2、get的时候,如果发现当前位置Entry中的key为null,会调用。
3、调用remove方法的时候,删除完当前位置的Entry之后,也会调用