ThreadLocal

我们知道线程也是一个「对象」,当线程这种对象想为我们提供一个「可以存取我们自定义变量的功能时」,来看下它是怎么做的。

一、Thread 中的成员变量ThreadLocalMap

  1. 这种功能通过成员变量java.lang.Thread#threadLocals来完成的,它是一个自定义Map类型:

    public class Thread implements Runnable {
        /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    

    可以看到它的:

    • null : 代表懒加载,只在你需要存数据时才会实例化一个ThreadLocalMap对象。
    • default : 访问修饰符,代表只想让java.lang下被访问,就是给java.lang.ThreadLocal用。
  2. 为什么不在Thread里面给我们暴露方法?

    设计者觉得暴露一个类似get()/put()方法给你,让你自己用任意的Object类型K/V操作太Low。

    所以他把这个功能「封装」为一个类,称为线程本地变量java.lang.ThreadLocal,每一个你需要的变量就是一个此类的实例。

二、ThreadLocal 实现

它们的关系是,Thread has a ThreadLocalMap has a Entry has a ThreadLocal

  1. 既然用Map存,那KeyValue分别是什么?

    • Key : 线程变量对象ThreadLocal
    • Value : 你自定义操作的那个数据Object
  2. 当然,这个内部Map设计意图肯定要对使用者透明

    所以我们不能直接操作这个Map,而是使用的ThreadLocal的三个public方法:get()/set()/remove()

    set 方法

    public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 简单的返回Thread 对象的成员变量threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    

    get 方法

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 这个Map 没有get方法只有getEntry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                // 你之前设置的值
                return (T)e.value;
        }
        return setInitialValue();
    }
    

    remove 方法

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

三、ThreaLocal 使用

  1. Example

    JavaDoc 中已经给出了,摘抄一下:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ThreadId {
        // Atomic integer containing the next thread ID to be assigned
        private static final AtomicInteger nextId = new AtomicInteger(0);
    
        // Thread local variable containing each thread's ID
        private static final ThreadLocal threadId =
            new ThreadLocal() {
                @Override protected Integer initialValue() {
                    return nextId.getAndIncrement();
            }
        };
    
        // Returns the current thread's unique ID, assigning it if necessary
        public static int get() {
            return threadId.get();
        }
    }
    
  2. 解释:初始化和set()方法

    首先,之所以要使用「匿名内部类」及其对象,是因为想让你在一行代码内就完成初始化。除此之外,initialValue()set()的实现代码区别不大。

    然后,一旦哪个Thread引用到这个ThreadLocal对象了,就会在自己的threadLocals中被设置一个值。

  3. 获取不必解释,就是从当前线程的Map中取即可。

四、WeakReference 的应用

1. 假设我们来设计那个Map和其中的Entry

  1. Map 和Entry

    static class ThreadLocalMap {
        
            static class Entry {
            final K key;
            V value;
        }
    }    
    

  2. 我们的使用场景是这样的

    // 这里注意,没有final 了
    private static ThreadLocal threadId =
        new ThreadLocal() {
            @Override protected Integer initialValue() {
                return nextId.getAndIncrement();
        }
    }
    

    当我们想不再使用threadId时,我们会把threadId设置为null

    public void method1(){
        // ... 一些操作
        threadId = null;
    }
    
  3. 此时,GC 会清除这个对象吗?

    分析之后我们发现其实大概率不会,因为那个ThreadLocal对象并非「不可达对象」,这是因为我们的Thread.threadLocals.Entry.key仍然引用着它。只有当Thread对象本身被销毁后它才会被回收,不过在Web 项目或RPC 项目中我们一般都使用线程池,所以一般来说线程很难被销毁。

    很明显,我们的设计有问题。那我们看看设计者是如何做的。

2. 实际代码

  1. 来看看实际代码

    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 {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        // ...
    }
    

    Entry是个弱引用。这意味着:一旦GC 确定ThreadLocal对象是「弱可达对象」时,它将清除所有引用此对象的「弱引用」,也就是说此时Entry也将被清除。

  2. 最后再来复习一下对象的「可到达性」

    从最强到最弱,不同的可到达性级别反映了对象的生命周期。在操作上,可将它们定义如下:

    1. 强可到达 对象:如果某一线程可以不必遍历所有引用对象而直接到达一个对象,则该对象是强可到达 对象。新创建的对象对于创建它的线程而言是强可到达对象。
    2. 软可到达 对象:如果一个对象不是强可到达对象,但通过遍历某一软引用可以到达它,则该对象是软可到达 对象。
    3. 弱可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象,但通过遍历弱引用可以到达它,则该对象是弱可到达 对象。当清除对某一弱可到达对象的弱引用时,便可以终止此对象了。
    4. 虚可到达 对象:如果一个对象既不是强可到达对象,也不是软可到达对象或弱可到达对象,它已经终止,并且某个虚引用在引用它,则该对象是虚可到达 对象。

    最后,当不能以上述任何方法到达某一对象时,该对象是不可到达 对象,因此可以回收此对象。

你可能感兴趣的:(ThreadLocal)