越努力,越幸运

微信公众号:JoonWhee。专注于Java原创知识交流,优秀技术文章、职场人生、面试经验分享。...

Java并发:InheritableThreadLocal详解

概述

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

InheritableThreadLocal是ThreadLocal的子类,可以先了解下ThreadLocalJava并发:ThreadLocal详解


1.定义

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


2.源码

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()方法确实可以达到使子线程与主线程互不影响的效果。

—————END—————



阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。微信公众号:JoonWhee,欢迎关注。 https://blog.csdn.net/v123411739/article/details/79117430
个人分类: Java并发
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭