ThreadLocal学习记录

ThreadLocal学习记录

ThreadLocal之前理解的一直很模糊,决定花点时间好好学习一下。最初接触ThreadLocal是在JVM的引用类型谈到弱引用(Weak Reference)的时候,会出现内存泄漏、脏数据等问题。

首先ThreadLocal从字面看是线程本地变量的意思,它不是一个线程,只是为每个线程维护了一个独立的变量副本。这个副本的数据是当前线程独享的,也就是说每个线程只可以访问自己的变量副本,同时进行各种操作时不会影响其他线程的变量副本。所以,ThreadLocal到底在什么场景下使用呢?当每个线程需要某些变量在线程中独立,但是在方法中共享,就可以使用ThreadLocal对象去设置这些变量。

public class Main{
    static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println(threadLocal.get());
            threadLocal.set(1);
            System.out.println(threadLocal.get());
        });

        Thread t2 = new Thread(()->{
            System.out.println(threadLocal.get());
            threadLocal.set(2);
            System.out.println(threadLocal.get());
        });

        t1.start();
        t1.join();
        t2.start();
    }
}

null
1
null
2

可以发现线程间共享TheadLocal对象,而Threadlocal对象维护的变量在线程间是隔离的。线程1执行过set后,线程2的ThreadLocal第一次get()值为null就可以说明。真要将ThreadLocal的内容写成文章记录,发现思路还是比较乱的,还是决定接着探讨ThreadLocal的原理,然后再从源码解释加深印象。

TheadLocal原理

先给出Thread 、ThreadLocal和ThreadLocalMap的关系图(图片来自《码出高效:Java开发手册》)。

从图中得出:

  • 一个线程有且只有一个ThreadLocalMap对象
  • 一个Entry对象的Key弱引用指向一个ThreadLocal对象
  • 一个ThreadlocalMap对象存储多个Entry对象
  • 一个ThreadLocal对象可以被多个线程共享
  • ThreadLocal对象不持有Value,Value由线程的Entry对象持有。

使用IDEA查看ThreadLocal源码,快捷键 Ctrl+F12 查看其中的方法和子类等。其中的ThreadLocal中有一个静态内部类ThreadLocalMap,而ThreadLocalMap中也有一个静态内部类Entry。同时ThreadLocal与ThreadLocalMap中都定义了三个重要的方法get()/getEntry()、set()、remove()。我们可以简单看下源码:

//ThreadLocal的get()方法
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); //发现最后的操作其实是ThreadLocalMap在实现
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

//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);
}

//ThreadLocal的remove()方法
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

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

查看其他两种方法也可以发现,ThreadLocal的上述三种方法都是由ThreadLocalMap完成实现的。再来看一下ThreadLocalMap的源码:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    .
    .
    .
    .
}

再来一张比较简单清晰的图:
在这里插入图片描述

实线表示强引用,虚线表示弱引用。对于强引用的回收,如果想取消强引用和某个对象之间的关联,可以显式地将引用赋值为null,这样可以使JVM在合适的时间就会回收该对象。所以,为了避免内存泄漏,我们的想法是这样的,将ThreadLocal对象引用置为null,这样ThreadLocal对象弱引用的key就会在下一次YGC的时候自动被回收.需要注意的是,这时候还存在着一条强引用链Thread对象引用->Thread->ThreadLocalMap -> entry -> value -> 线程变量 ,如果不做任何操作就会使value无法被回收,也无法被使用,导致内存泄漏。
为了防止这种现象出现,ThreadLocal提供有get()、set()、和remove()方法,将key==null的value置为null,这样取消强引用关联,使得jvm在合适的时间将value回收,防止出现内存泄漏。但是,源码告诉我们get()和set()并不能完全防止内存泄漏,remove()才是最保险的。具体分析可以看这篇博客我惊了!!!ThreadLocal 源码存在内存泄露的 Bug!!!

总结一下:

ThreadLocal问题一:产生脏数据

使用线城池重复调用线程对象的时候,与该线程绑定的ThreadLocal变量也会重复使用,当不及时remove()与线程有关的信息,那么下一次调用该线程若不是用set()设置初始值,就会get()到之前线程被使用过的信息,就产生了脏数据。

ThreadLocal问题二:内存泄漏

上面已经分析过,不及时使用remove()操作,会导致value无法被回收,该块内存也无法使用,导致内存泄漏。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值