前情提要
本文需要有一定JVM内存知识
本文需要有一定Thread知识
本文需要有一定ThreadPool知识
文章分发
www.yuque.com/docs/share/… 《ThreadLocal及其扩展类》
文章主体开始
本文主要描述ThreadLocal及其存在的问题
基础知识-Java的引用
强引用:在虚拟机GC的时候,可达性分析算法下仍然存活的不会被垃圾回收器回收的。当内存空间不足的时候,就会抛出OOM异常。一般认为就是虚拟机栈一直指向某块堆内存,就是一个强引用。
弱引用:JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
ThreadLocal
// ThreadLocal
public T get() {
// 1,当前线程的 Thread
Thread t = Thread.currentThread();
// 2,通过当前线程拿到 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 3,通过 ThreadLocalMap 和 this(ThreadLoca)
// 拿到 静态内部类 Entry
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;
}
static class ThreadLocalMap {
// 3.1,静态内部类是继承 WeakReference 软引用 的
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
复制代码
Thread
// Thread
public static native Thread currentThread();
ThreadLocal.ThreadLocalMap threadLocals = null;
复制代码
上面的注解解析
注解1解析
Thread t = Thread.currentThread();
通过 Thread 类的静态 native 方法,获取到当前线程对象
注解2解析
ThreadLocalMap map = getMap(t);
通过Thread的当前线程拿到 ThreadLocalMap,getMap是ThreadLocal的方法,但是指向的是Thread的一个属性
ThreadLocal.ThreadLocalMap threadLocals = null;
扫描二维码关注公众号,回复: 13555432 查看本文章
如果第一次进来,会返回null,就会去设置默认值。
注解3解析
ThreadLocalMap.Entry e = map.getEntry(this);
static class Entry extends WeakReference<ThreadLocal<?>>
通过 ThreadLocalMap 和 this(ThreadLoca) ,拿到 静态内部类 Entry
静态内部类 Entry 是继承 WeakReference 软引用
小结
ThreadLocal其实就是Thread的一个属性,里面存放了一些该线程里面的数据
使用注意事项
每次使用完必须要remove,下面是原因的解析
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author xuegao
* @version 1.0
* @date 2021/12/11 17:59
*/
public class ThreadLocalV2Service {
static class LocalVariable {
// 总共有5M
private byte[] local = new byte[1024 * 1024 * 5];
}
static final ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(6, 6, 1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
static ThreadLocal<LocalVariable> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; ++i) {
POOL_EXECUTOR.execute(new Runnable() {
@Override
public void run() {
LocalVariable localVariable = new LocalVariable();
ThreadLocalV2Service.THREAD_LOCAL.set(localVariable);
System.out.println("thread name end:" + Thread.currentThread().getName() +
", value:" + ThreadLocalV2Service.THREAD_LOCAL.get());
ThreadLocalV2Service.THREAD_LOCAL.remove();
}
});
Thread.sleep(1000);
}
// 是否让key失效,都不影响。只要持有的线程存在,都无法回收。
// ThreadLocalOutOfMemoryTest.localVariable = null;
System.out.println("pool execute over");
}
// 作者:城南码农
// 链接:https://juejin.cn/post/6982121384533032991
// 来源:稀土掘金
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
}
复制代码
下面四图是idea的profiler的使用,把代码运行起来就可以进来这里了,或者使用jdk自带的jvisualvm.exe
如果有什么问题,请使用vx联系我吧,在下方
下面两张图是注释后和注释前的区别,指 THREAD_LOCAL.remove()
由上面第一张图可知,如果在使用完threadlocal后进行了remove删除,那么GC后堆内存就基本上被垃圾回收了
第二张图是使用完成后没有进行remove的,我们有6个线程,每个线程持有5M内存,所有大约有30M内存在堆中无法被回收
原因是因为我们定义的是线程池,线程池并不会因为gc,而将线程池里面的6个线程直接销毁,还是会一直持有,所以形成的是,6个thread是栈帧,堆内存是thread里面的threadlocal属性,每个都有5M大小,栈帧一直存在,堆内存会被一直视为有人使用,而永远无法被gc回收掉,造成了内存泄漏。(虽然我们往线程池里面execute了很多任务,但是线程池的maximumPoolSize只有6个,后续的都会覆盖之前的)
实际项目中,多线程技术的广泛使用,如果代码中有threadlocal没有remove,就会造成大量的堆内存被浪费
使用缺陷
tip:父子线程,在web工程中指的是,一个完整的请求是一个线程,但是在这个请求里面,使用或者开启了另外的线程
只能在单个线程下传递数据,无法在父子线程等情况传递数据
以下是代码事例
/**
* @author xuegao
* @version 1.0
* @date 2021/12/11 14:58
*/
public class MyThreadLocalTest1 {
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) {
THREAD_LOCAL.set("luanqi");
System.out.println("main线程 = " + THREAD_LOCAL.get());
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("新的线程 = " + THREAD_LOCAL.get());
}
}).start();
}
}
// main线程 = luanqi
// 新的线程 = null
// 思路有来自
// 作者:我有一只喵喵
// 链接:https://juejin.cn/post/7022529092519985160
// 来源:稀土掘金
// 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
复制代码
实际中的使用场景
1,单个线程中,数据的透传
其他
vx公众号:Java雪糕