ThreadLocal原理剖析

ThreadLocal原理剖析

JAVA四种引用类型(强软弱虚)

分析ThreadLocal之前先了解java下面的四种引用类型:

强引用

形式:Person person = new Person();
特点:如果没有把person置为空的话,在继续在堆里面加东西的时候,就算heap装不下了,宁愿出现OOM,也不回收person
在这里插入图片描述

软引用

形式:SoftReference<byte[]> SR = new SoftReference<>(new byte[1024]);
特点:heap如果装不下的时候,gc会回收软引用
在这里插入图片描述
应用场景:经常用作缓存

弱引用

形式:WeakReference<Person> Wr = new WeakReference<>(new Person());
特点:当gc看到弱引用的时候,gc会回收它。
引用场景:解决ThreadLocal的内存泄露问题

虚引用(了解即可)

形式:PhantomReference<Person> PR = new PhantomReference<Person>(new Person(),new ReferenceQueue());
特点:管理直接内存,起到通知作用,当gc回收虚引用的时候,会把引用的对象放到一个队列里面。

ThreadLocal原理解析(深入源码)

我们看看以下的代码:

public class ThreadLocalTest {
    static ThreadLocal<Integer> threadLocal = new ThreadLocal();
    public static void main(String[] args)  {

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(threadLocal.get());
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                threadLocal.set(10);
            }
        }).start();
    }
}

运行之后控制台显示:
在这里插入图片描述
我们可以看到我们在第二个线程把10作为ThreadLocal的set方法的参数传进去,但是第二个线程是读不到10的,这就说明了,ThreadLocal是起到了隔离线程的一个作用。接下来,我们进入ThreadLocal的set方法看看发生了什么:

public void set(T value) {
		//取得当前线程
        Thread t = Thread.currentThread();
        //把当前线程作为参数传入getMap获得一个Map对象
        ThreadLocalMap map = getMap(t);
        //把ThreadLocal对象作为key传入到上面那个map中
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
}

接下来我们看看getMap方法是如何获取ThreadLocalMap对象的:

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}

我们惊奇的发现getMap方法返回的是线程的一个属性,我们进入Thread的源码看看这个属性:

ThreadLocal.ThreadLocalMap threadLocals = null;

我们发现ThreadLocalMap 是ThreadLocal的一个静态内部类,经过如上分析我们不难发现,ThreadLocal可以实现线程隔离的原因了,我们很容易可以知道每个线程都有一个ThreadLocalMap ,当我们在当前线程调用ThreadLocal的get方法的时候,是从他的ThreadLocalMap里面找到,所以不会和其他线程出现混乱。接下我们来看看ThreadLocalMap是如何存放数据的,我们看到ThreadLocalMap的一个内部类Entry,我们知道这键值对的意思:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
}

我们惊奇的发现ThreadLocalMap 里面存放的键值对是一个弱引用,并且他的key是ThreadLocal的对象,并且把它作为参数传入父类的构造器,就是说当没有强引用指向key的时候,他会被gc垃圾回收掉。然后ThreadLocalMap是以数组的方式存储键值对的:

 private Entry[] table;

我们接着回到ThreadLocal的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);
   }

我们可以看到map.set(this, value);,所以我们可以知道threadLocal.set(10);这个方法就是将threadLocal实例作为key,10作为value存入到当前线程的ThreadLocalMap中,经过以上分析我们可以画出以下的图片(实线表示强引用,虚线表示软引用):
在这里插入图片描述

ThreadLocal的内存泄露问题

为什么要使用弱引用?

我们观察上面的图,如果Entry是强引用,当我们在当前线程中不使用ThreadLocal的时候,我们把它设为了null,但是因为Entry是的key强引用的指向上面不需要再使用的ThreadLocal对象,他会一直存放在内存中,从而造成了内存泄漏,但是如果Entry键值对是弱引用的话,当ThreadLocal不使用的时候,就没东西指向它了,它自然就被gc回收避免了内存泄漏。

可能造成内存泄漏的第二种原因

我们接着上面的问题,当key被回收了之后,他是一个null,但是我们的value的值还是强引用的指向了一个对象,因为key为null我们也不可能使用到这个value了,但是它又不能被gc回收所以也造成了内存泄漏。我们的解决方式就是在把ThreadLocal设为空的时候,一定要调用他的remove方法,把这个键值对移除,尽管我们调用的set方法的时候,会把ThreadLocalMap的key为null的键值对移除,因为我们开发的时候一个线程是长时间运行的,同时set方法我们也不知道啥时候调用,所以作为一种编程习惯,在不用ThreadLocal的时候要remove掉。

ThreadLocal的还可能出现的问题

在开发中,我们经常会使用线程池技术,如果我们在线程结束的时候不把当前线程的ThreadLocalMap清空掉,会出现很多问题,因为我们线程结束的时候会把线程放入线程池中,当我们再次从线程池中取线程的时候,当取到一样的线程,我们使用的就是上次的线程的ThreadLocalMap。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值