二、变量可继承的ThreadLocal=》InheritableThreadLocal

2.1 源码注释

 

这个类扩展ThreadLocal,以提供从父线程到子线程的值的继承:当创建子线程时,子线程会接收父元素所具有值的所有可继承线程局部变量的初始值。正常情况下,子线程的变量值与父线程的相同;然而,子线程可复写childValue方法来自定义获取父类变量。


当变量(例如,用户ID、事务ID)中维护的每个线程属性必须自动传输到创建的任何子线程时,使用InheritableThreadLocal优于ThreadLocal。

 

2.2 源码剖析

 

1.子线程启动时,调用init方法,如果父线程有InheritableThreadLocal变量,则在子线程也生成一份

 

下图是Thread类在init时执行的逻辑:

ThreadLocal终极源码剖析(二)_java

调用createInheritedMap方法,并调用childValue方法复制一份变量给子线程

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

 

2.支持用户自定义childValue函数,用以子类获取父类变量值的转换:父类变量--childValue转换函数---》子类变量

 

InheritableThreadLocal默认childValue函数是直接返回:

protected T childValue(T parentValue) {
    return parentValue;
}

 

用户可在创建InheritableThreadLocal变量时,覆盖childValue函数,见3.3测试

 

2.3  功能测试

 

package threadLocal;


/**
 * 
 * @ClassName:MyInheritableThreadLocal
 * @Description:可继承线程本地变量
 * @author denny.zhang
 * @date 2017年12月7日下午5:24:40
 */

public class MyInheritableThreadLocal{
    //线程本地共享变量
    private static final InheritableThreadLocal<Object> threadLocal = new InheritableThreadLocal<Object>(){
        /**
         * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
         */

        @Override
        protected Object initialValue()
        
{
            System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!");
            return null;
        }

        @Override
        protected Object childValue(Object parentValue) 
{
            return (Integer)parentValue*2;
        }

    };

    public static void main(String[] args){
        //主线程设置1
        threadLocal.set(1);
        //1.开启任务1线程
        new Thread(new MyIntegerTask("IntegerTask1")).start();
        //2.中间休息3秒,用以测试数据差异
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //开启任务2线程
        new Thread(new MyIntegerTask("IntegerTask2")).start();
    }

    /**
     * 
     * @ClassName:MyIntegerTask
     * @Description:整形递增线程
     * @author diandian.zhang
     * @date 2017年12月4日上午10:00:41
     */

    public static class MyIntegerTask implements Runnable{
        private String name;

        MyIntegerTask(String name)
        {
            this.name = name;
        }

        @Override
        public void run()
        
{
            for(int i = 0; i < 5; i++)
            {
                // ThreadLocal.get方法获取线程变量
                if(null == MyInheritableThreadLocal.threadLocal.get())
                {
                    // ThreadLocal.set方法设置线程变量
                    MyInheritableThreadLocal.threadLocal.set(0);
                    System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0");
                }
                else
                {
                    int num = (Integer)MyInheritableThreadLocal.threadLocal.get();
                    System.out.println("i="+i+"[线程" + name + "]get=" + num);
                    MyInheritableThreadLocal.threadLocal.set(num + 1);
                    System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyInheritableThreadLocal.threadLocal.get());
                    //当i=3即循环4次时,移除当前线程key
                    if(i == 3)
                    {
                        System.out.println("i="+i+"[线程" + name + "],remove" );
                        MyInheritableThreadLocal.threadLocal.remove();
                    }
                }
                try
                {
                    Thread.sleep(1000);
                }
                catch (InterruptedException e)
                {
                    e.printStackTrace();
                }
            }  
        }
    }
}

 

运行结果:

主线程变量值=1-----》主线程中变量值1
i=0[线程IntegerTask1]get=2-----》子线程1中变量值=2*1=2,验证通过!
i=0[线程IntegerTask1]往threadLocal中set: 3
i=1[线程IntegerTask1]get=3
i=1[线程IntegerTask1]往threadLocal中set: 4
i=2[线程IntegerTask1]get=4
i=2[线程IntegerTask1]往threadLocal中set: 5
i=0[线程IntegerTask2]get=2-----》主线程2中变量值=2*1=2,验证通过!
i=0[线程IntegerTask2]往threadLocal中set: 3
i=3[线程IntegerTask1]get=5
i=3[线程IntegerTask1]往threadLocal中set: 6
i=3[线程IntegerTask1],remove
i=1[线程IntegerTask2]get=3
i=1[线程IntegerTask2]往threadLocal中set: 4
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask1]当前线程不存在缓存,set 0
i=2[线程IntegerTask2]get=4
i=2[线程IntegerTask2]往threadLocal中set: 5
i=3[线程IntegerTask2]get=5
i=3[线程IntegerTask2]往threadLocal中set: 6
i=3[线程IntegerTask2],remove
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask2]当前线程不存在缓存,set 0

 

如上图,分析结果我们可知,

1.子线程根据childValue函数获取到了父线程的变量值。

2.多线程InheritableThreadLocal变量各自维护,无竞争关系。

 

2.4 应用场景

 

子线程变量数据依赖父线程变量,且自定义赋值函数。

例如:

开启多线程执行任务时,总任务名称叫mainTask 子任务名称依次递增mainTask-subTask1、mainTask-subTask2、mainTask-subTaskN等等

 

三、总结

本文分析了ThreadLocal原理、set(散列算法原理和测试验证,再哈希扩容)、get、remove源码,实际中的应用场景以及功能测试验证。最后又分析了InheritableThreadLocal,使用该类子线程会继承父线程变量,并自定义赋值函数。

读完本文,相信大家对ThreadLocal一点也不担心了哈哈!

 

需要注意的点:

1.ThreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争!目的是线程本地变量且只能单个线程内维护使用。

2.InheritableThreadLocal对比ThreadLocal唯一不同是子线程会继承父线程变量,并自定义赋值函数。

3.项目如果使用了线程池,那么小心线程回收后ThreadLocal、InheritableThreadLocal变量要remove,否则线程池回收后,变量还在内存中,后果不堪设想!(例如Tomcat容器的线程池,可以在拦截器中处理:extends HandlerInterceptorAdapter,然后复写afterCompletion方法,remove掉变量!)