ThreadLocal源码分析解读



前言

可能很多小伙伴都听说过ThreadLocal,但是在具体面试的时候,却对底层原理以及如何使用感到困惑,我在写这篇文章的时候,和你们是一样的心情
面试官常问的问题:
1.ThreadLocal听说过吗?你知道他底层是怎么实现的吗?
2.知道ThreadLocal内存泄漏问题吗?怎么解决的?

一、ThreadLocal是什么?

ThreadLocal直译过来就是线程本地的意思,实际上ThreadLocal是线程本地变量,每个线程独有,且不与其他线程变量冲突,和普通变量不同,它是与当前线程关联,每个线程都可以使用其get或set方法来访问自己的独立初始化变量副本,通常是 private static final。这就实现了线程隔离

二、底层实现原理

1.ThreadLocalMap

ThreadLocal底层实现了ThreadLocalMap,ThreadLocalMap实现了内部类Entry,将线程与vale保存。

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

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

2.ThreadLocal主要方法

2.1 set(T value)为线程绑定变量

public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取该线程对象的ThreadLocalMap
        if (map != null)
            map.set(this, value);//如果map不为空,执行set操作,以当前threadLocal对象为key,实际存储对象为value进行set操作
        else
            createMap(t, value);//如果map为空,则为该线程创建ThreadLocalMap
    }
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

从getMap与CreateMap可以看出ThreadLocal只不过是一个入口,而真正的变量副本是绑定在线程上的

2.2 get(),获取当前线程绑定的变量副本

 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();//如果map为空,也就是第一次没有调用set直接get(或者调用过set,又调用了remove)时,为其设定初始值
    }
  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

现在我们可以知道ThreadLocal底层实现逻辑:

  1. ThreadLocal只不过是绑定变量副本的一个入口
  2. 每一个线程都有一个ThreadLocalMap对象,这个ThreadLocalMap持有对对象的引用
  3. ThreadLocalMap以当前的threadLocal对象为key,以真正的存储对象为value。get()方法时通过threadLocal实例就可以找到绑定在当前线程上的副本对象
    直接来看,就是类似于一个Map<Key,Value>的形式,一个线程对应一个存储对象

3.ThreadLocal的内存泄漏问题

我们看到ThreadLocal是底层是一个ThreadLocalMap,而它底层是由Entry实现的,而Entry是继承于WeakReference,这里可能有小伙伴还对强软弱虚引用不太了解,这里再加强一下

3.1强引用

如果一个对象具有强引用,那么垃圾回收器是不会回收他的,当内存空间不足,会报出OutOfMemoryError的错,是程序异常或终止,也不会通过回收强引用对象来解决问题

3.2软引用

如果内存空间充足,那么垃圾回收处理器是不会回收它的,在内存空间不足的情况下才会回收软引用对象,可以用来实现内存敏感的高速缓存

3.3弱引用

如果一个对象只具有弱引用,那就类似于可有可物的生活用品。弱引用与软引用的区别在于:只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它 所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程, 因此不一定会很快发现那些只具有弱引用的对象。 弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

3.4虚引用

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收。 虚引用主要用来跟踪对象被垃圾回收的活动

说完了强软弱虚引用,我们回到正题,为什么会出现内存泄漏呢,对于ThreadLocalMap中的Entry它的key为一个弱引用,而Value是一个强引用,ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。
但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。
在这里插入图片描述
实现代表强引用,虚线代表弱引用

解决方式:通过自身的remove方法解决

    /**
         * Remove the entry for key.
         */
        private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

4.ThreadLocal使用实例

public class ThreadLocalDemo {

   public static ThreadLocal<String> threadLocal =  new ThreadLocal<String>();

    public static void main(String[] args) {
        ThreadLocalDemo.threadLocal.set("hello world main");
        System.out.println("创建新线程前,主线程" + Thread.currentThread().getName() + "的threadlocal字符值为:"  + ThreadLocalDemo.threadLocal.get());

        try {
            Thread thread = new Thread() {
                @Override
                public void run() {
                    ThreadLocalDemo.threadLocal.set("new thread");
                    System.out.println("新线程" + Thread.currentThread().getName() + "的threadlocal字符值为:" + ThreadLocalDemo.threadLocal.get());
                }
            };
            thread.start();
            thread.join();
        } catch (Exception e) {
            System.out.println(e);
        }
        System.out.println("创建新线程后,主线程" + Thread.currentThread().getName() + "的threadlocal字符值为:"  + ThreadLocalDemo.threadLocal.get());

    }
}

总结

这篇ThreadLocal,你get到了那些点?我们在之后会遇到更多的问题,慢慢成长,才是最好的自己

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值