深入学习ThreadLocal和InheritableThreadLocal

来源:互联网 发布:2017网络综艺节目排名 编辑:程序博客网 时间:2024/06/05 16:45

最新看项目代码时, 发现有地方用到了InheritableThreadLocal,之前只用过ThreadLocal,于是就查了点资料,看了下源码,稍微学习一下。

本文地址:http://blog.csdn.net/v123411739/article/details/78697099

ThreadLocal

1.ThreadLocal的定义

这个类提供了线程局部变量。 这些变量与正常的变量不同,因为每个访问一个线程的线程(通过它的get或set方法)都有自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类(例如,用户ID或事务ID)中的私有静态字段。只要线程处于活动状态并且ThreadLocal实例可以访问,每个线程就拥有对其线程局部变量副本的隐式引用; 在一个线程消失之后,线程本地实例的所有副本都会被垃圾收集(除非存在对这些副本的其他引用)。

2.ThreadLocalMap的定义

ThreadLocalMap是一个自定义哈希映射,仅适用于维护线程本地值。 没有任何操作被导出到ThreadLocal类之外。 该类是封装私有的,允许在Thread类中声明字段。 为了处理非常大和长期使用的用法,哈希表条目使用WeakReferences作为键。 但是,由于不使用引用队列,所以只有当表开始空间不足时才能删除旧条目。

ThreadLocalMap是ThreadLocal的静态内部类,是一个类似HashMap的实现,有兴趣的可以去看下源码,用法和HashMap没有区别,这里不在赘述。

3.ThreadLoca.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);    }
    /**     * Get the map associated with a ThreadLocal. Overridden in     * InheritableThreadLocal.     *     * @param  t the current thread     * @return the map     */    ThreadLocalMap getMap(Thread t) {        return t.threadLocals;    }
    /* ThreadLocal values pertaining to this thread. This map is maintained     * by the ThreadLocal class. */    ThreadLocal.ThreadLocalMap threadLocals = null;
先拿到当前线程,再使用getMap方法拿到当前线程的threadLocals变量,如果threadLocals不为空,则将当前ThreadLocal作为key,传入的值作为value,插入threadLocals;如果threadLocals为空则新建一个,然后将ThreadLocal作为key,传入的值作为value,插入threadLocals。注意此处的threadLocals变量是一个ThreadLocalMap,是Thread的一个局部变量,因此它只与当前线程绑定。

4.ThreadLoca.get()方法

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

    /**     * Variant of set() to establish initialValue. Used instead     * of set() in case user has overridden the set() method.     *     * @return the initial value     */    private T setInitialValue() {        T value = initialValue();        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);        return value;    }
    protected T initialValue() {        return null;    }

    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }

跟set方法差不多,先拿到当前的线程,再使用getMap方法拿到当前线程的threadLocals变量,如果threadLocals不为空,则将当前ThreadLocal作为key,拿到对应的值;如果threadLocals为空,则会创建一个新的ThreadLocalMap,并将当前的ThreadLocal作为key,null作为value,插入到ThreadLocalMap,并返回null。注意上面的 initialValue()方法为protected,官方注释写到:如果程序员希望线程局部变量具有非null的初始值,则必须对ThreadLocal进行子类化,并重写此方法。 通常,将使用匿名内部类。

5.ThreadLocal.remove()方法

     public void remove() {         ThreadLocalMap m = getMap(Thread.currentThread());         if (m != null)             m.remove(this);     }
方法很简单,拿到当前线程的threadLocals属性,如果不为空,则将key为当前ThreadLocal的键值对移除。

6.功能测试

package com.chillax.test;import java.util.concurrent.TimeUnit;/** * ThreadLocal测试 *  * @author JoonWhee * @Date 2017年12月2日 */public class TestThreadLocal {    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();    public static void main(String args[]) throws InterruptedException {        Thread threadOne = new ThreadOne(); // 线程1        Thread threadTo = new ThreadTo(); // 线程2        threadTo.start(); // 线程2开始执行        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待线程2执行完毕        threadOne.start(); // 线程1开始执行        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待线程1执行完毕        // 此时线程1和线程2都已经设置过值, 此处输出为空, 说明子线程与父线程之间也是互不影响的        System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());    }    private static class ThreadOne extends Thread {        @Override        public void run() {            // 此时线程2已经调用过set(456), 此处输出为空, 说明线程之间是互不影响的            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());            threadLocal.set(123);            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());        }    }    private static class ThreadTo extends Thread {        @Override        public void run() {            threadLocal.set(456); // 设置当前ThreadLocal的值为456            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());        }    }}

输出结果:

Thread-1: 456
Thread-0: null
Thread-0: 123
main: null

总结:

1.每个线程都有一个ThreadLocalMap 类型的 threadLocals 属性。
2.ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。
3.当我们通过 threadLocal.set(new Integer(123)); ,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123))的threadlocal,value 就是值new Integer(123)。
4.当我们通过 threadlocal.get() 方法的时候,首先会根据这个线程得到这个线程的 threadLocals 属性,然后由于这个属性放的是键值对,我们就可以根据键 threadlocal 拿到值。 注意,这时候这个键 threadlocal 和 我们 set 方法的时候的那个键 threadlocal 是一样的,所以我们能够拿到相同的值。

5.ThreadLocalMap 的get/set/remove方法跟HashMap的内部实现都基本一样,都是通过hashCode和Entry长度-1进行位于运算得到我们想要找的索引位置,如果该索引位置的键值对不止1个,则遍历并找到我们所需的键值对然后进行相应操作即可,ThreadLocalMap 的hashCode跟HashMap不一样,有兴趣的可以去看看。

InheritableThreadLocal

1.InheritableThreadLocal的定义

InheritableThreadLocal继承了ThreadLocal,此类扩展了ThreadLocal以提供从父线程到子线程的值的继承:当创建子线程时,子线程接收父线程具有的所有可继承线程局部变量的初始值。 通常子线程的值与父线程的值是一致的。 但是,通过重写此类中的childValue方法,可以将子线程的值作为父线程的任意函数。

2.InheritableThreadLocal的源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {    /**     * Computes the child's initial value for this inheritable thread-local     * variable as a function of the parent's value at the time the child     * thread is created.  This method is called from within the parent     * thread before the child is started.     * <p>     * This method merely returns its input argument, and should be overridden     * if a different behavior is desired.     *     * @param parentValue the parent thread's value     * @return the child thread's initial value     */    protected T childValue(T parentValue) {        return parentValue;    }    /**     * Get the map associated with a ThreadLocal.     *     * @param t the current thread     */    ThreadLocalMap getMap(Thread t) {       return t.inheritableThreadLocals;    }    /**     * Create the map associated with a ThreadLocal.     *     * @param t the current thread     * @param firstValue value for the initial entry of the table.     */    void createMap(Thread t, T firstValue) {        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);    }}
InheritableThreadLocal的源码很短,只有3个很短的方法,我们主要关注getMap()方法和creatMap()方法,这两个方法都是重写的,跟ThreadLocal中的差别在于把ThreadLocal中的threadLocals换成了inheritableThreadLocals,这两个变量都是ThreadLocalMap类型,并且都是Thread类的属性。

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

3.InheritableThreadLocal继承父线程的值

上文的定义说道了InheritableThreadLocal会继承父线程的值,这是InheritableThreadLocal被创造出来的意义,具体是怎么实现的?

让我们从子线程被创建出来开始看起

    public Thread() {        init(null, null, "Thread-" + nextThreadNum(), 0);    }
    private void init(ThreadGroup g, Runnable target, String name,                      long stackSize) {        init(g, target, name, stackSize, null);    }
    private void init(ThreadGroup g, Runnable target, String name,            long stackSize, AccessControlContext acc) {// ... 省略掉一部分代码if (parent.inheritableThreadLocals != null)  this.inheritableThreadLocals =      ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);// ... 省略掉一部分代码}
    static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {        return new ThreadLocalMap(parentMap);    }
        /**         * Construct a new map including all Inheritable ThreadLocals         * from given parent map. Called only by createInheritedMap.         *         * @param parentMap the map associated with parent thread.         */        private ThreadLocalMap(ThreadLocalMap parentMap) {            Entry[] parentTable = parentMap.table;            int len = parentTable.length;            setThreshold(len);            table = new Entry[len];// 创建跟父线程相同大小的table            for (int j = 0; j < len; j++) {// 遍历父线程的inheritableThreadLocals, 在上面第3个代码块作为参数传下来                Entry e = parentTable[j];                if (e != null) {                    @SuppressWarnings("unchecked")                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();// 拿到每个键值对的key, 即ThreadLocal对象                    if (key != null) {                        Object value = key.childValue(e.value);// 此处会调用InheritableThreadLocal重写的方法, 默认直接返回入参值                        Entry c = new Entry(key, value);// 使用key和value构造一个Entry                        int h = key.threadLocalHashCode & (len - 1);// 通过位与运算找到索引位置                        while (table[h] != null)// 如果该索引位置已经被占,则寻找下一个索引位置                            h = nextIndex(h, len);                        table[h] = c;// 将Entry放在对应的位置                        size++;                    }                }            }        }
这是线程被创建的整个流程,从第3个代码块我们可以知道当父线程的inheritableThreadLocals不为空时,当前线程的inheritableThreadLocals属性值会被直接创建,并被赋予跟父线程的inheritableThreadLocals属性一样的值,从最后一个代码块看出来(已在代码中做详细注释)。

此时我们知道了,当一个子线程创建的时候,会把父线程的inheritableThreadLocals属性的值继承到自己的inheritableThreadLocals属性。那么现在的问题是父线程的inheritableThreadLocals属性会有值吗?因为上文提到的ThreadLocal中,我们知道set()方法时,是把键值对放在threadLocals属性。这就要提到刚才说的InheritableThreadLocal重写的getMap()方法,因为InheritableThreadLocal类的set()和get()方法都是用的父类即ThreadLocal的方法,而从ThreadLocal的源码中我们知道,ThreadLocal的get()、set()、remove()方法都会先调用getMap()方法,而InheritableThreadLocal重写了该方法,因此此时返回的ThreadLocalMap为inheritableThreadLocals,所以我们知道了,当定义为InheritableThreadLocal时,操作的属性为inheritableThreadLocals而不是threadLocals。

    /**     * Get the map associated with a ThreadLocal.     *     * @param t the current thread     */    ThreadLocalMap getMap(Thread t) {       return t.inheritableThreadLocals;    }

4.功能测试

下面代码是对InheritableThreadLocal继承父线程的值的验证,可以看出,子线程确实拿到了父线程的值。



5.InheritableThreadLocal变量的可见性探讨

package com.chillax.test;import java.util.concurrent.TimeUnit;/** * InheritableThreadLocal可见性测试 *  * @author JoonWhee * @author http://blog.csdn.net/v123411739 * @Date 2017年12月2日 */public class TestInheritableThreadLocal2 {    public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();    public static ThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<Integer>();    public static ThreadLocal<User> mutableInheritableThreadLocal = new InheritableThreadLocal<User>();    public static ThreadLocal<User> mutableInheritableThreadLocalTo = new InheritableThreadLocal<User>();    public static ThreadLocal<String> immutableInheritableThreadLocal = new InheritableThreadLocal<String>();    public static ThreadLocal<String> immutableInheritableThreadLocalTo = new InheritableThreadLocal<String>();    public static void main(String args[]) throws InterruptedException {        // 测试0.ThreadLocal普通测试;        // 结论0: ThreadLocal下子线程获取不到父线程的值        threadLocal.set(new Integer(123)); // 父线程初始化        Thread thread = new MyThread();        thread.start();        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待子线程执行完毕        System.out.println("main = " + threadLocal.get());        System.out.println();        // 测试1.InheritableThreadLocal普通测试;        // 结论1: InheritableThreadLocal下子线程可以获取父线程的值        inheritableThreadLocal.set(new Integer(123)); // 父线程初始化        Thread threads = new MyThreadTo();        threads.start();        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待子线程执行完毕        System.out.println("main = " + inheritableThreadLocal.get());        System.out.println();        // 测试2.父线程和子线程的传递关系测试: 可变对象, 父线程初始化;        // 结论2: 父线程初始化, Thread Construct浅拷贝, 共用索引, 子线程先get()对象, 再修改对象的属性,        // 父线程跟着变, 注意: 此处子线程如果没有先get()直接使用set()一个新对象, 父线程是不会跟着变的        mutableInheritableThreadLocal.set(new User("joon"));// 2.1父线程初始化        Thread TestThread = new TestThread(); // 2.2先初始化父线程再创建子线程, 确保子线程能继承到父线程的User        TestThread.start(); // 开始执行子进程        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待子线程执行完毕        System.out.println("main = " + mutableInheritableThreadLocal.get()); // 2.5此处输出值为子线程修改的值, 因此可得出上述结论2        System.out.println();        // 测试3.父线程和子线程的传递关系测试: 可变对象, 父线程不初始化;        // 结论3: 父线程没有初始化, 子线程初始化, 无Thread Construct浅拷贝, 子线程和主线程都是单独引用, 不同对象,        // 子线程修改父线程不跟着变        Thread TestThreadTo = new TestThreadTo(); // 3.1创建子进程        TestThreadTo.start();        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待子线程执行完毕        System.out.println("main = " + mutableInheritableThreadLocalTo.get()); // 3.3此处输出为null, 可得出上述结论3        System.out.println();        // 测试4.父线程和子线程的传递关系测试: 不可变对象, 父线程初始化;        // 结论4: 父线程初始化, Thread Construct浅拷贝, 但由于不可变对象由于每次都是新对象, 所以互不影响        immutableInheritableThreadLocal.set("joon");// 4.1父线程初始化        Thread TestThreadTre = new TestThreadTre(); // 4.2先初始化父线程再创建子线程, 确保子线程能继承到父线程的值        TestThreadTre.start(); // 执行子进程        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待子线程执行完毕        System.out.println("main = " + immutableInheritableThreadLocal.get()); // 4.5此处输出为父线程的值, 因此可得出上述结论4        System.out.println();        // 测试5.父线程和子线程的传递关系测试: 不可变对象, 父线程不初始化;        // 结论5: 父线程没有初始化, 子线程初始化, 无Thread Construct浅拷贝, 子线程和父线程操作不同对象, 互不影响        Thread TestThreadFour = new TestThreadFour(); // 5.1创建子线程        TestThreadFour.start();        TimeUnit.MILLISECONDS.sleep(100); // 睡眠, 以等待子线程执行完毕        System.out.println("main = " + immutableInheritableThreadLocalTo.get()); // 5.3此处输出为空, 因此可得出上述结论5    }    private static class MyThread extends Thread {        @Override        public void run() {            System.out.println("MyThread = " + threadLocal.get());        }    }    private static class MyThreadTo extends Thread {        @Override        public void run() {            System.out.println("inheritableThreadLocal = " + inheritableThreadLocal.get());        }    }    private static class TestThread extends Thread {        @Override        public void run() {            // 2.3此处输出父线程的初始化对象值, 代表子线程确实继承了父线程的对象值            System.out.println("TestThread.before = " + mutableInheritableThreadLocal.get());            // 2.4子类拿到对象并修改            mutableInheritableThreadLocal.get().setName("whee");            System.out.println("mutableInheritableThreadLocal = " + mutableInheritableThreadLocal.get());        }    }    private static class TestThreadTo extends Thread {        @Override        public void run() {            mutableInheritableThreadLocalTo.set(new User("whee"));// 3.2子线程调用set方法            System.out.println("mutableInheritableThreadLocalTo = " + mutableInheritableThreadLocalTo.get());        }    }    private static class TestThreadTre extends Thread {        @Override        public void run() {            // 4.3此处输出父线程初始化的值, 代表子线程确实继承了父线程的对象值            System.out.println("TestThreadTre.before = " + immutableInheritableThreadLocal.get());            // 4.4子线程调用set方法            immutableInheritableThreadLocal.set("whee");            System.out.println("immutableInheritableThreadLocal = " + immutableInheritableThreadLocal.get());        }    }    private static class TestThreadFour extends Thread {        @Override        public void run() {            immutableInheritableThreadLocalTo.set("whee");// 5.2子线程调用set方法            System.out.println("immutableInheritableThreadLocalTo = " + immutableInheritableThreadLocalTo.get());        }    }    private static class User {        String name;        public User(String name) {            this.name = name;        }        public String getName() {            return name;        }        public void setName(String name) {            this.name = name;        }        @Override        public String toString() {            return "User [name=" + name + "]";        }    }}

输出结果:



代码中都注释写清楚了,主要是根据存放可变对象 (User) 和不可变对象 (String)继续测试,根据输出结果可以得出以下结论:

1.对于可变对象:父线程初始化, 因为Thread Construct浅拷贝, 共用索引, 子线程修改父线程跟着变; 父线程不初始化, 子线程初始化, 无Thread Construct浅拷贝, 子线程和父线程都是单独引用, 不同对象, 子线程修改父线程不跟着变。

2.对于不可变对象:不可变对象由于每次都是新对象, 所以无论父线程初始化与否,子线程和父线程都互不影响。


从上面两条结论可知,子线程只能通过修改可变性(Mutable)对象对主线程才是可见的,即才能将修改传递给主线程,但这不是一种好的实践,不建议使用,为了保护线程的安全性,一般建议只传递不可变(Immuable)对象,即没有状态的对象。

虽然说不建议使用,但是有时候还是会碰到这种情况,如果想在修改子线程可变对象,同时不影响主线程,可以通过重写childValue()方法来实现。

6.重写childValue()方法实现子线程与父线程之间互不影响

package com.chillax.test;import java.util.HashMap;import java.util.Map;import java.util.concurrent.TimeUnit;/** * 子线程与父线程实现完全互不影响 * @author JoonWhee * @author http://blog.csdn.net/v123411739 * @Date 2017年12月2日 */public class TestInheritableThreadLocal3 {    private static final ThreadLocal<Map<Object, Object>> testThreadLocal = new InheritableThreadLocal<Map<Object, Object>>();    private static final ThreadLocal<Map<Object, Object>> threadLocal = new InheritableThreadLocalMap<Map<Object, Object>>();    public static void main(String args[]) throws InterruptedException {        // 下面的测试1在上文已经做过(上文的测试2), 此处拿出来是为了进行比较        // 测试1: 可变对象, 父线程初始化, 子线程先获取对象再修改对象值        // 结论1: 子线程可以通过先获取对象再修改的方式影响父线程的对象值         Map<Object, Object> map = new HashMap<>();        map.put("aa", 123);        testThreadLocal.set(map);   // 父线程进行初始化                Thread testThreadone = new TestThread();   // 创建子线程        testThreadone.start();        TimeUnit.MILLISECONDS.sleep(100);   // 父线程睡眠, 以等待子线程执行完毕         System.out.println("main = " + testThreadLocal.get());  // 此处输出为子线程的值, 说明子线程影响父线程的对象值        System.out.println();                // 测试2: 可变对象, 父线程初始化, 子线程先获取对象再修改对象值        // 结论2: 通过重写childValue()实现子线程与父线程的互不影响        Map<Object, Object> mapTo = new HashMap<>();        mapTo.put("aa", 123);        threadLocal.set(mapTo);   // 父线程进行初始化                Thread testThread = new TestThreadTo();   // 创建子线程        testThread.start();        TimeUnit.MILLISECONDS.sleep(100);   // 父线程睡眠, 以等待子线程执行完毕         System.out.println("main = " + threadLocal.get());  // 此处输出为父线程的值, 说明子线程与父线程已经互不影响            }        private static class TestThread extends Thread {        @Override        public void run() {            // 此处输出父线程的初始化对象值, 代表子线程确实继承了父线程的对象值            System.out.println("TestThread.before = " + testThreadLocal.get());            // 子类拿到对象并修改            testThreadLocal.get().put("aa", 456);            System.out.println("testThreadLocal = " + testThreadLocal.get());        }    }        private static class TestThreadTo extends Thread {        @Override        public void run() {            // 此处输出父线程的初始化对象值, 代表子线程确实继承了父线程的对象值            System.out.println("TestThreadTo.before = " + threadLocal.get());            // 子类拿到对象并修改            threadLocal.get().put("aa", 456);            System.out.println("threadLocal = " + threadLocal.get());        }    }    private static final class InheritableThreadLocalMap<T extends Map<Object, Object>>            extends InheritableThreadLocal<Map<Object, Object>> {        // 重写ThreadLocal中的方法        protected Map<Object, Object> initialValue() {            return new HashMap<Object, Object>();        }        // 重写InheritableThreadLocal中的方法        protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {            if (parentValue != null) {                // 返回浅拷贝, 以达到使子线程无法影响主线程的目的                return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();            } else {                return null;            }        }    }}
输出结果

TestThread.before = {aa=123}
testThreadLocal = {aa=456}
main = {aa=456}

TestThreadTo.before = {aa=123}
threadLocal = {aa=456}
main = {aa=123}


通过结果,我们可以看出重写childValue()方法确实可以达到使子线程与主线程互不影响的效果。

原创粉丝点击