ThreadLocal源码阅读

作者: 我犟不过你 | 来源:发表于2021-05-20 11:03 被阅读0次

首先我们模拟个场景,看看ThreadLocal的效果是怎么样的。

有如下代码,有一个学生类,定义一个全局变量ThreadLocal,然后再main方法分别启动一个线程去操作这个ThreadLocal,第一个线程set内容,第二个线程get,看看是否能获取到:

/**
 * @description: ThreadLocal与弱引用
 * @author:weirx
 * @date:2021/5/20 10:07
 * @version:3.0
 */
public class ThreadLocalAndWeakReference {

    static class Student {
        private String name;

        public Student(String name) {
            this.name = name;
        }
    }

    static ThreadLocal threadLocal = new ThreadLocal();

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            threadLocal.set(new Student("zhangsan"));
        }).start();

        Thread.sleep(1000);
        new Thread(() -> {
            System.out.println(threadLocal.get());
        }).start();
    }
}

结果:

null

相信了解ThreadLocal的同学都知道这是必然的。

接下来我们通过分析其set方法,看看是如何做到的.

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //根据当前线程获取map
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //将this(当前ThreadLocal)设置为key,将value设置为map的value。
            map.set(this, value);
        else
            createMap(t, value);
    }

getMap是个什么鬼?

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

我们通过以上源码,发现通过t线程获取其threadlocals。每个线程有自己的ThreadLocal,到这里就能明白为什么多个线程使用一个ThreadLocal不会相互产生影响。

线程的ThreadLocal

接下来我们继续看看ThreadLocal是如何实现key,value的缓存的,继续跟踪ThreadLocalMap的set方法:

private void set(ThreadLocal<?> key, Object value) {

            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)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            //根据传过来的k、v new了一个Entry
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

通过上述代码我们主要关心这个Entry:

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

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

我们发现这个Entry是集成弱引用的,何为弱引用参考https://www.jianshu.com/p/5d1d5bb86152,通过其构造方法,发现其key仍然是这个ThreadLocal,v仍然是我们的student对象,但是设置key时使用的是supper,也就是弱引用的构造,证明其key是一个弱引用,而其value则是一个强引用。这里记一下,后面会豁然开朗。

我们能够得出结论:
作为key的这个ThreadLocal对象,被两个引用同时引用着;
1)我们开始创建的全局变量threadLocal通过强引用引用着;
2)在Entry当中,被一个弱引用所引用着。

那么我们看看为什么要用弱引用?如果使用强引用会怎么样?

如果两个都是强引用,那么当我们的线程未终止运行,或者threadLocal被设置为null了,那么这个ThreadLocal对象仍然会被一个强引用所引用,将导致不可被回收,从而发生内存泄漏。而弱引用只要发生回收,其生命周期就结束了。

那么即使使用了弱引用就不会发生内存泄漏了吗?
在Entry中,key是ThreadLocal,被弱引用,当它被回收时,这个key就被置为null,其value值就无法被访问到了,也就是前面我们创建的student,如果student在强引用着其他的对象,那么仍然会造成内存泄漏。

最后给出使用的建议:如果我们的ThreadLocal使用完成后,一定要记得手动释放,调用其ThreadLocal的remove方法,防止内存泄漏

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

应用场景:spring中使用ThreadLocal来设计TransactionSynchronizationManager类,实现了事务管理与数据访问服务的解耦,同时也保证了多线程环境下connection的线程安全问题。

相关文章

网友评论

    本文标题:ThreadLocal源码阅读

    本文链接:https://www.haomeiwen.com/subject/hvevjltx.html