ThreadLocal源码阅读

什么是ThreadLocal

ThreadLocal 是 Java 里一种特殊变量,它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。用于线程内共享

 

使用demo

static   ThreadLocal threadLocal=new ThreadLocal();
    public static void main(String[] args) {
        //<1>set
        threadLocal.set("dd");
        test();
    }
    public static  void  test(){
        //线程内共享 打印dd
        System.out.println(threadLocal.get());
    }

ThreadLocal

<1>set方法

java.lang.ThreadLocal#set

    public void set(T value) {
        //获得当前线程对象
        Thread t = Thread.currentThread();
        //<3>获得t的threadLocals的成员变量 也就是ThreadLocalMap
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            //<4>ThreaLocal为key value为我们的值
            map.set(this, value);
        else
            //为t初始化一个ThreaLocalMap并设置值
            createMap(t, value);
    }

<3>getMap

java.lang.ThreadLocal#getMap

//返回ThreadLocal.ThreadLocalMap  
 ThreadLocal.ThreadLocalMap getMap(Thread t) {
        //获得Thread的成员变量threadLocals
        return t.threadLocals;
    }

从上面可以看出我们的数据都是存储在ThreadLocalMap,ThreadLocalMap就是一个普通的map通过ThreadLocal对象为key

<4>set

java.lang.ThreadLocal.ThreadLocalMap#set

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

        // We don't use a fast path as with get() because it is at
        // least as common to use set() to create new entries as
        // it is to replace existing ones, in which case, a fast
        // path would fail more often than not.
        //ThreaLocalMap内部通能数组来保存 通过Entry封装
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);

        for (ThreadLocal.ThreadLocalMap.Entry e = tab[i];
             e != null;
             //threadLocalMap是通过线性探测法来解决hash碰撞,当出现hash碰撞查找数组的下一个位置
             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;
            }
        }
        //<5>通过内部内Entry 来存储 key为ThreadLocal value为我们set的值 并存储到数组的具体位置
        tab[i] = new  Entry(key, value); 
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

<5>Entry

 /**
     * ThreadLocalMap静态内部类
     * WeakReference 为弱引用,当弱引用引用的的对象没有被强引用时也会被回收
     * 这里可以发现 value是强引用
     * super(k) key为ThreadLocal 传给了父类 则是弱引用
     */
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

<6>get方法

 public T get() {
        //获得当前线程
        Thread t = Thread.currentThread();
        //<3>获得线程map
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            //根据当前threadLoca 找到对应的entry
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                //获得value
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果为null则触发初始化
        return setInitialValue();
    }

    private T setInitialValue() {
        //初始化
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocal.ThreadLocalMap map = getMap(t);
        //如果map不为null
        if (map != null)
            //直接set进去
            map.set(this, value);
        else
            //创建map设置进去
            createMap(t, value);
        return value;
    }

    /**
     * 默认为空实现 我们可以继承重写达到默认值的效果
     * @return
     */
    protected T initialValue() {
        return null;
    }

 

ThreadLocal内存泄露

或者我们使用线程池技术,没有及时remove导致Threadlocal常驻到Thread

根据<5>处我们可以看到ThradLocal没有被强引用时就会被垃圾回收 即Entry Key会被回收,但是value还被Entry引用 ,Entry被ThreadLocalMap引用 ThreadLocalMap被Thread引用,所以在线程未被回收前对象会一直不会被回收,如果使用线程池技术 可能导致内存泄露

如:

public static void main(String[] args) {
        test();
    }
    public static  void  test(){
        ThreadLocal threadLocal=new ThreadLocal();
        threadLocal.set(new Student());;
    }

弱引用Entry的keyThreadLocal对象会被回收,但是Entry还持有value的引用 ThreadLocalMap持有Entry引用  Thread持有ThreadLocalMap引用 导致内存泄露,所以当使用完要手动调用remove方法

public static  void  test(){
        ThreadLocal threadLocal=new ThreadLocal();
        threadLocal.set("ddd");;
        threadLocal.remove();
    }

 key使用弱引用是下次使用Thread.remove方法会回收key为null的

private void remove(ThreadLocal<?> key) {
    //使用hash方式,计算当前ThreadLocal变量所在table数组位置
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //再次循环判断是否在为ThreadLocal变量所在table数组位置
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        if (e.get() == key) {
            //调用WeakReference的clear方法清除对ThreadLocal的弱引用
            e.clear();
            //清理key为null的元素
            expungeStaleEntry(i);
            return;
        }
    }
}

 

总结

1.ThreadLocal本质获取当前线程对象的成员变量ThreadLocalMap ThreadLocalMap通过ThreadLocal作为key存储相应的value value通过Entry封装

2.使用map的原因是因为一个线程可以定义多个ThreadLocal

posted @ 2020-04-23 14:48  意犹未尽  阅读(213)  评论(0编辑  收藏  举报