Java 之 ThreadLocal 详解

作者: jwfy | 来源:发表于2019-02-05 23:02 被阅读2次

    ThreadLocal 在java中是充当“线程本地变量”使用的,在每个线程都会创建该对象从而实现线程之间的隔离,具有一定程度上的线程安全(也就是说不是绝对的线程安全,后面会细说这一点)

    public T get()   // 获取当前线程的threadlocal存储的数据
    public void set(T value)  // 为当前线程的threadlocal设置数据
    public void remove()  // 移除threadlocal数据
    protected T initialValue()  // 保护方法,构建初始值
    

    接下来依次分析每个方法具体实现细节

    public T get() {
        Thread t = Thread.currentThread();
        // 获取当前执行线程t
        ThreadLocalMap map = getMap(t);
        // 这个getMap方法是获取当前线程t持有的ThreadLocalMap对象信息
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            // map的KEY 是 this 也就是当前这个threadLocal对象本身
            // map的VALUE 是 ThreadLocalMap.Entry 一个entry对象(初次看貌似和hashmap的entry类似)
            if (e != null) {
                // 如果当前的value有数据,则直接取出器entry的value对象,返回
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 其余情况全部默认返回初始化值数据,具体看下面的方法
        return setInitialValue();
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
        // 返回线程t的threadlocals参数信息
    }
    
    private T setInitialValue() {
        T value = initialValue();
        // 默认返回null
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // 再次获取当前线程的threadlocals对象
        if (map != null)
            map.set(this, value);
            // 设置的值是key:this,value:null
        else
            createMap(t, value);
            // 把当前的值塞入到map中
        return value;
    }
    

    线程有一个名字叫threadlocals的Map对象,键值对数据是<ThreadLocal,Value>,在获取的时候先看map是否存在,如果存在直接获取键值对信息,否则填充一个默认初始值塞入到线程map中

    再来看看ThreadLocalMap 对象的类型

    static class ThreadLocalMap {
    
        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    

    ThreadLocalMap 原来和一般意义上的HashMap并没有太多的关系,只是采取了类似的名称而已,而且需要注意到其使用了WeakReference 弱引用,当发生GC操作的时候其会被回收操作

    因为threadlocals是线程的一个对象,那么不同的线程map是也是完全隔离的,也正是因为这一点才使得其具备了一定程度的线程安全的特性,不过并不是严格意义上的线程安全。例如下面这个例子说明为什么不是绝对的线程安全

    image

    如上图所示在一个JVM里面包含了2个线程A和B,在各自线程中的threadlocals对象分别是a和b,age和sex参数都是基础类型,是完全隔离开的,但是却共同引用了同一个Student对象信息,那么就存在A线程修改Student信息 ,然后B获取的Student信息是A修改之后的可能,打破了线程隔离这个特性。

    第一点结论:ThreadLocal不具备线程安全的特性,想要拥有线程安全,Value必须是不可变对象

    上面有提到ThreadLocalMap中的Entry 是WeakReference<ThreadLocal<?>>弱引用,每次GC操作都会被回收的,使得KEY为NULL,从而ThreadLocalMap存在<NULL, Object> 的对象信息,从而出现内存泄露的问题

    为了防止这种问题,所以在使用完threadlocal对象后,需要显示调用remove方法

     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             // 在没有被GC回收期,从ThreadLocalMap 中主动移除该数据
             m.remove(this);
    }
     
     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);
                 // 如果在tab中找到了对应的键值对,则直接设置其引用为null
                // 也就是使得tab[i] = null ,使得GC能够回收干净,不出现内存泄露的问题
                return;
            }
        }
    }
    

    第二点结论:使用ThreadLocal完毕后确保不出现内存泄露的问题,需要显示调用remove方法

    总结:ThreadLocal可以用来存储一些线程临时的不可变对象,并不能用来处理线程共享,需要明确注意其组成结构

    相关文章

      网友评论

        本文标题:Java 之 ThreadLocal 详解

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