在谈线程的ThreadLocal之前需要了解一下java的引用
引用分类
1.强引用:我们平时通过new一个对象产生的对象名就是一个强引用,这是我们用的最广泛的引用
House house=new House();
强引用对象是即使内存再吃紧而且GC Roots
可大,垃圾回收都不会回收这个对象
2.软引用:通过SoftReference
进行对象指向
House house=new House();
SoftReference<House> reference=new SoftReference<House>(house);
软引用会把对象放到垃圾回收的队列中,在系统产生oom内存不足
之前`就有可能去回收对象, 以保证系统的正常运行。
3.弱引用:比软引用更弱的引用,通过WeakReference
进行对象指向
House house=new House();
WeakReference<House> reference=new WeakReference<House>(house);
弱应用的对象是在垃圾回收的时候更容易被回收,就是可以减少对强应用对象的挟持,减少内存泄漏
4.虚引用:最弱引用,通过PhantomReference
进行对象指向
House house=new House();
PhantomReference<House> reference=new PhantomReference<House>(house);
虚引用只要垃圾回收就是回收该引用指向的对象,用的比较少,一般只用于获取对象回收时系统发出的通知
ThreadLocal
前面我们列举完了java的四大引用类型,然后我们现在就可以开始分析ThreadLocal了
ThreadLocal的作用
多线程之间进行数据共享,当多个线程同时操作一个统一对象的变量时,会产生数据错乱问题,其实就是同步问题,而我们的
ThreadLocal
正是为了解决这个问题。
ThreadLocal
在每个线程中对该变量会创建一个副本,即每个线程内部都会有一个该变量,这样在同步的时候不同的线程操作的都是一个变量的副本,互相之间是不会有交集的。
实现原理
Thread->ThreadLocalMap->多个ThreadLocal变量
上面这个链状关系就是ThreadLocal
实现多线程并发问题的核心原理。
我们简单解释一下
就是一个线程里会有一个容器ThreadLocalMap
容器的代码长这样
static class ThreadLocalMap {
//每个线程都有一个存储变量副本的数组
private ThreadLocal.ThreadLocalMap.Entry[] table;
//以ThreadLocal的弱应用为键 值为变量副本
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}
//为线程添加变量副本
private void set(ThreadLocal<?> var1, Object var2) {
...
//给table数组元素赋值
var3[var5] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
...
}
}
一个线程里可以有多个ThreadLocal
对象,每个 ThreadLocal
的都对应一个变量副本。
通过这个东西我们可以为每个线程创建一个变量副本,只要通过这个ThreadLocal
来查找,最后是存储在这个ThreadLocalMap
里面。
然后重点是每个ThreadLocal
都是一个弱引用,这样可以有效的避免内存泄漏。
问题
但是如果我们看过阿里的《码出搞笑 Java开发手册》的都应该知道,ThreadLocal
有两个副作用,一个就是脏数据,另一个就是内存泄漏。
1.内存泄漏
那我们是不是打脸,是的,因为ThreadLocal
在线程里面的声明是这样的
public class ThreadLocal<T> {
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> var0) {
return new ThreadLocal.SuppliedThreadLocal(var0);
}
}
返回的是一个static关键字声明的ThreadLocal
,那即使gc也不能释放这个对象。
2.脏数据
就是在线程复用的时候如果ThreadLocal没有进行重新赋值,就是没有调用它的set()
方法的话,你通过这个ThreadLocal查找回来的还是老数据,这就是脏数据产生的源头
解决方案:
两个问题的解决方案都是及时的调用remove()
方法清理即可
网友评论