ThreadLocal 如何保障对象只被以后线程拜访呢?

上面让咱们一起深刻 ThreadLocal 的外部实现。

咱们须要关注的天然是 ThreadLocal 的 set() 办法和 get() 办法。

set

先从 set() 办法说起:

    /**     * Sets the current thread's copy of this thread-local variable     * to the specified value.  Most subclasses will have no need to     * override this method, relying solely on the {@link #initialValue}     * method to set the values of thread-locals.     *     * @param value the value to be stored in the current thread's copy of     *        this thread-local.     */    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

在 set 时,首先取得以后线程对象,而后通过 getMap() 办法拿到线程的 ThreadLocalMap ,并将值存入 ThreadLocalMap 中。

而 ThreadLocalMap 能够了解为一个 Map (尽管不是,然而你能够把它简略地了解成 HashMap ),然而它是定义在 Thread 外部的成员。

留神上面的定义是从 Thread 类中摘出来的

    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;

而设置到 ThreadLocal 中的数据,也正是写入了 threadLocals 的这个 Map 。

其中, key 为 ThreadLocal 以后对象, value 就是咱们须要的值。

而 threadLocals 自身就保留了以后本人所在线程的所有“局部变量”,也就是一个 ThreadLocal 变量的汇合。

在这里也给想晋升的开发人们安利一个福利:Java高级进阶笔记,完整版PDF文档点击此处收费支付。

get

在进行 get() 办法操作时,天然就是将这个 Map 中的数据拿进去。

    /**     * Returns the value in the current thread's copy of this     * thread-local variable.  If the variable has no value for the     * current thread, it is first initialized to the value returned     * by an invocation of the {@link #initialValue} method.     *     * @return the current thread's value of this thread-local     */    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();    }

get() 办法先获得以后线程的 ThreadLocalMap 对象,而后通过将本人作为 key 获得外部的理论数据。

Thread.exit()

在理解了 ThreadLocal 的外部实现后,咱们天然会引出一个问题:

那就是这些变量是保护在 Thread 类外部的( ThreadLocalMap 定义所在类),这也意味着只有线程不退出,对象的援用将始终存在。

当线程退出时, Thread 类会进行一些清理工作,其中就包含清理 ThreadLocalMap 。

    /**     * This method is called by the system to give a Thread     * a chance to clean up before it actually exits.     */    private void exit() {        if (group != null) {            group.threadTerminated(this);            group = null;        }        /* Aggressively null out all reference fields: see bug 4006245 */        target = null;        /* Speed the release of some of these resources */        threadLocals = null;        inheritableThreadLocals = null;        inheritedAccessControlContext = null;        blocker = null;        uncaughtExceptionHandler = null;    }

因而,应用线程池就意味着以后线程未必会退出(比方固定大小的线程池,线程总是存在)。

如果这样,将一些大的对象设置到 ThreadLocal 中(它理论保留在线程持有的 ThreadLocalMap 内),可能会使零碎呈现内存透露的可能。

这里我的意思是:你设置了对象到 Threadlocal 中,然而不清理它,在你应用几次后,这个对象也不再有用了,然而它却无奈被回收。

此时,如果你心愿及时回收对象,最好应用 ThreadLocal.remove() 办法将这个变量移除,就像咱们习惯性地敞开数据库连贯一样。

如果你的确不须要这个对象了,就应该通知虚拟机,请把它回收,避免内存透露。

tl = null

另外一种乏味的状况是 JDK 也可能容许你像开释一般变量一样开释 ThreadLocal 。

比方,咱们有时候为了减速垃圾回收,会特意写出相似 obj = null 的代码。

如果这么做,那么 obj 所指向的对象就会更容易地被垃圾回收器发现,从而减速回收。

同理,如果对于 ThreadLocal 的变量,咱们也手动将其设置为 null ,比方 tl = null ,那么这个 ThreadLocal 对应的所有线程的局部变量都有可能被回收。

这外面的神秘是什么呢?

先来看一个简略的例子。

package com.shockang.study.java.concurrent.thread_local;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.CountDownLatch;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class ThreadLocalDemo_Gc {    static volatile ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {        protected void finalize() throws Throwable {            System.out.println(this.toString() + " is gc");        }    };    static volatile CountDownLatch cd = new CountDownLatch(10000);    public static class ParseDate implements Runnable {        int i = 0;        public ParseDate(int i) {            this.i = i;        }        public void run() {            try {                if (tl.get() == null) {                    tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {                        protected void finalize() throws Throwable {                            System.out.println(this.toString() + " is gc");                        }                    });                    System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");                }                Date t = tl.get().parse("2015-03-29 19:29:" + i % 60);            } catch (ParseException e) {                e.printStackTrace();            } finally {                cd.countDown();            }        }    }    public static void main(String[] args) throws InterruptedException {        ExecutorService es = Executors.newFixedThreadPool(10);        for (int i = 0; i < 10000; i++) {            es.execute(new ParseDate(i));        }        cd.await();        System.out.println("mission complete!!");        tl = null;        System.gc();        System.out.println("first GC complete!!");        //在设置ThreadLocal的时候,会革除ThreadLocalMap中的有效对象        tl = new ThreadLocal<SimpleDateFormat>();        cd = new CountDownLatch(10000);        for (int i = 0; i < 10000; i++) {            es.execute(new ParseDate(i));        }        cd.await();        Thread.sleep(1000);        System.gc();        System.out.println("second GC complete!!");    }}

上述案例是为了跟踪 ThreadLocal 对象,以及外部 SimpleDateFormat 对象的垃圾回收。

为此,咱们重载了 finalize() 办法。

这样,咱们在对象被回收时,就能够看到它们的形迹。

在主函数 main 中,先后进行了两次工作提交,每次 10000 个工作。

在第一次工作提交后,咱们将 tl 设置为 null ,并进行一次 GC 。

接着,咱们进行第二次工作提交,实现后,再进行一次 GC 。

执行上述代码,最有可能的一种输入如下所示。

19:create SimpleDateFormat15:create SimpleDateFormat17:create SimpleDateFormat18:create SimpleDateFormat20:create SimpleDateFormat14:create SimpleDateFormat11:create SimpleDateFormat12:create SimpleDateFormat13:create SimpleDateFormat16:create SimpleDateFormatmission complete!!first GC complete!!com.shockang.study.java.concurrent.thread_local.ThreadLocalDemo_Gc$1@5041865d is gc11:create SimpleDateFormat14:create SimpleDateFormat20:create SimpleDateFormat12:create SimpleDateFormat16:create SimpleDateFormat13:create SimpleDateFormat18:create SimpleDateFormat15:create SimpleDateFormat17:create SimpleDateFormat19:create SimpleDateFormatsecond GC complete!!

留神这些输入所代表的含意。

首先,线程池中 10 个线程都各自创立了一个 SimpleDateFormat 对象实例。

接着进行第一次 GC ,能够看到 ThreadLocal 对象被回收了(这里应用了匿名类,所以类名看起来有点怪,这个类就是结尾创立的 t 对象)。

提交第 2 次工作,这次一样也创立了 10 个 SimpleDateFormat 对象,而后进行第二次 GC 。

在第二次 GC 后,第一次创立的 10 个 SimpleDateFormat 的子类实例全副被回收。

尽管咱们没有手工 remove (这些对象,然而零碎仍然有可能回收它们)。

ThreadLocal.ThreadLocalMap

要理解下面的回收机制,咱们须要更进一步理解 ThreadLocal.ThreadLocalMap 的实现。

之前咱们说过, ThreadLocalMap 是一个相似 HashMap 的货色。

更精确地说,它更加相似 WeakHashMap。

ThreadLocalMap 的实现应用了弱援用。

弱援用是比强援用弱得多的援用。

Java 虚拟机在垃圾回收时,如果发现弱援用,就会立刻回收。

ThreadLocalMap 外部由一系列 Entry 形成,每一个 Entry 都是 WeakReference< ThreadLocal>。

        /**         * 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;            }        }

这里的参数 k 就是 Map 的 key , v 就是 Map 的 value ,其中 k 也是 ThreadLocal 实例,作为弱援用应用。

super(k) 就是调用了 WeakReference 的构造函数

因而,尽管这里应用 ThreadLocal 作为 Map 的 key ,然而实际上,它并不真的持有 Threadlocal 的援用。

而当 ThreadLocal 的内部强援用被回收时, ThreadLocalMap 中的 key 就会变成 null 。

当零碎进行 ThreadLocalMap 清理时(比方将新的变量退出表中,就会主动进行一次清理,尽管 JDK 不定会进行一次彻底的扫描,但显然在这个案例中,它见效了),就会将这些垃圾数据回收。

ThreadLocal 的回收机制

ThreadLocal 的回收机制,如图所示。