ThreadLocal使用与原理探索

ThreadLocal原理探索

ThreadLocal与synchronized的区别是:

ThreadLocal:每个线程单独有一个副本

synchronized:共用同一个资源,加锁

显然ThreadLocal是用空间换时间

ThreadLocal大致存储是这样的:

  1. 每个线程有一个ThreadLocalMap,即使不同的ThreadLocal实例,只要是同一个线程他们共享的都是同一个ThreadLocalMap
  2. ThreadLocalMap以ThreadLocal实例为key,且设置key为弱引用进行存储
    1. 设置key为弱引用的好处是,我不用这个ThreadLocalMap的时候,我只需要显示将变量赋值为null,因为它没有强引用了,我还设置它是弱引用,那GC就直接回收了,这样就不会造成内存泄漏的问题。【当然如果线程迟迟不结束,如果这个ThreadLocal对象还被被回收了,那么Map中key虽然没有了,但是value还在,而这个Map的生命周期和线程生命周期一致,因此我们当前线程不使用ThreadLocal了,就调用其remove放法,进行释放,否则仍然会造成内存泄漏】
  3. 当然如果我们只是当前线程不需要使用ThreadLocal了,那么我们在当前线程直接手动调用remove()方法即可
    image-20210203082439254

接下来我试着剖析一波

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

可以看出,首先获取当前线程的ThreadLocalMap,如果没有创建一波,它是以当前ThreadLocal实例对象为key进行存储

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

可以看出,首先获取当前线程的ThreadLocalMap,如果有则获取当前首先获取当前线程的ThreadLocal实例对象的Entry,然后取出值,否则调用方法和set源代码一样,只不过设置默认值null

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

可以看出,首先获取当前线程的ThreadLocalMap,然后移除当前ThreadLocal实例对象为key的value

整个测试

里面许多的注释是从最初探讨的时候写的,有的不一定对,以我上访分析源码为准

/**
 * @author ningxinjie
 * @date 2021/2/3
 */
// https://blog.csdn.net/weixin_42388901/article/details/96491793
public class ThreadLocalTest {
    // 测试下来可以看到,threadLocal果然是内部按照线程为key,每个线程有各自的ThreadLocalMap,本线程直接取出来的就是本线程的ThreadLocalMap;
    // 其中remove针对的也是本线程的,因此本线程不使用的时候最好手动调用一下,因为ThreadLocalMap的key是弱引用


    // 每个ThreadLocal实例,内部为每个线程创建一个ThreadLocalMap,这个Map以当前ThreadLocal实例为key,且内部存放key是弱引用
    // 为什么设置成弱引用呢?这样我们不用这个对象的时候直接将这个对象=null即可,如果设置成强引用,那么内部的map的key是这个对象。这个对象一直是key存放在map中

    // 	ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,
    // 	这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
    static ThreadLocal threadLocal = new ThreadLocal(); // 目前我们不把它设置为null,他就是强引用,因此目前不会被回收
    static ThreadLocal threadLocal2 = new ThreadLocal(); // 都讲jdk建议设置成private static这样强引用就不会被回收,我们不用手动置null,这样没有了强引用就变成了弱引用,gc即回收

    public static void main(String[] args) throws Exception {
        threadLocal.set("this is main");
        new Thread(new Runnable() {
            @Override
            public void run() {
                threadLocal.set("this is thead01");
                System.out.println("thread01 set ok");
                System.out.println(threadLocal.get());
            }
        },"thead01").start();
        Thread.sleep(200);

        // 同一个线程共用同一个ThreadLocalMap,但是key是ThreadLocal实例 ,这里的实例是threadLocal2 ,因此没有值
        System.out.println(threadLocal2.get());
        Object o = threadLocal.get();
        System.out.println(o);

        // 移除当前线程的ThreadLocalMap
        threadLocal.remove();
        System.in.read();
    }
posted @ 2021-02-03 23:39  程序杰杰  阅读(51)  评论(0编辑  收藏  举报