找回密码
 立即注册
首页 业界区 业界 浅谈InheritableThreadLocal---线程可继承的小书包 ...

浅谈InheritableThreadLocal---线程可继承的小书包

赖秀竹 5 小时前
在前文中我们讲过ThreadLocal,相当于是每个线程有一个小书包,线程之间的小书包是隔离的,只存放了属于当前线程自己的变量,因此不会发生数据安全的问题。
(前文博客浅谈ThreadLocal----每个线程一个小书包  https://www.cnblogs.com/jilodream/p/19118986)
但是有时任务太繁重时,父线程希望new出新的子线程来为自己的业务提供帮助,同时希望子线程在处理时,也能用到自己保存在ThreadLocal中的变量。
现在有三种办法:
(1)直接把父线程的ThreadLocalMap传递给子线程,让子线程直接拿去用
如:
  1. sonThread.threadLocals=parent.threadLocals
复制代码
(2)父线程在创建子线程时将子线程需要用到的ThreadLocal数据,专门指定。
如:
  1. Thread sonThread=new Thread(new Runnable() {
  2.             @Override
  3.             public void run() {
  4.                 threadLocal1.set(xxx1); //手动指定
  5.                 threadLocal2.set(xxx2); //手动指定
  6.                 threadLocal3.set(xxx3); //手动指定
  7.             }
  8.         });
复制代码
(3)将父线程中threadLocals 中的数据,打包整理后,统一传递给子线程。
第一种显然不行,如果两者指向了相同的Map 会导致缓存数据被共享,父子线程存在并发读写,又会导致安全问题。
方法二虽然灵活,但是指定起来过于繁琐,每个子线程都要单独设置一遍。
java选用的是方法三,但是实现起来稍有不同:
java 定义了一个InheritableThreadLocal类。通过这个类来实现线程可继承的ThreadLocal
定义中的:
Inheritable  [ɪn'herɪtəbl]
adj.   可继承的,会遗传的
InheritableThreadLocal指可以继承/遗传的ThreadLocal。接下来看源码:
在Thread类中,定义了以下属性:
  1.     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
复制代码
类似于thread.threadLocals,这是给InheritableThreadLocal 来存储可以继承的属性的Map。
InheritableThreadLocal类的源码,它继承了ThreadLocal:
  1. public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  2.     /**
  3.      * Creates an inheritable thread local variable.
  4.      */
  5.     public InheritableThreadLocal() {}
  6.     /**
  7.      * Computes the child's initial value for this inheritable thread-local
  8.      * variable as a function of the parent's value at the time the child
  9.      * thread is created.  This method is called from within the parent
  10.      * thread before the child is started.
  11.      * <p>
  12.      * This method merely returns its input argument, and should be overridden
  13.      * if a different behavior is desired.
  14.      *
  15.      * @param parentValue the parent thread's value
  16.      * @return the child thread's initial value
  17.      */
  18.     protected T childValue(T parentValue) {
  19.         return parentValue;
  20.     }
  21.     /**
  22.      * Get the map associated with a ThreadLocal.
  23.      *
  24.      * @param t the current thread
  25.      */
  26.     ThreadLocalMap getMap(Thread t) {
  27.        return t.inheritableThreadLocals;
  28.     }
  29.     /**
  30.      * Create the map associated with a ThreadLocal.
  31.      *
  32.      * @param t the current thread
  33.      * @param firstValue value for the initial entry of the table.
  34.      */
  35.     void createMap(Thread t, T firstValue) {
  36.         t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
  37.     }
  38. }
复制代码
通过源码我们可以发现,它的用法和实现与ThreadLocal基本相同,但是它重写了父类(ThreadLocal)的几个方法:
(1)
首先是getMap(),重写的目的是当需要操作map对象时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )请使用inheritableThreadLocals而不是再是父类中的t.threadLocals写法,这样所有使用的map就都切换到了
ThreadLocal.ThreadLocalMap inheritableThreadLocals这个属性上来了。
(2)
其次是createMap() 方法,该方法的主要目的是初始化Map对象
这里强调了初始化的是 t.inheritableThreadLocals 属性。为啥没直接用getMap() 方法来获取 t.inheritableThreadLocals呢?这是由于 t.inheritableThreadLocals 还没有初始化,返回是null,你调用getMap()没有意义。
(3)
最后是 childValue() 方法,它是指当发生继承动作时,父类中的存储的变量转化为子类对象的转化转换。这里直接抽象成方法了,方便大家重写自己的InheritableThreadLocal类时,可以直接重写该方法。
如我们想前边加一个标签,代表是子线程专用,亦或者进行翻译转换等等,总之方便你自己实现转换。
来看下继承动作具体是怎么操作的:
继承主要发生在主线程创建子线程程时,我们来看下Thread的构造方法源码,经过一堆跳转最后会跳转进这个方法:
  1.     private Thread(ThreadGroup g, Runnable target, String name,
  2.                    long stackSize, AccessControlContext acc,
  3.                    boolean inheritThreadLocals) {
  4.         //....
  5.         
  6.         if (inheritThreadLocals && parent.inheritableThreadLocals != null)
  7.             this.inheritableThreadLocals =
  8.                 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
  9.         /* Stash the specified stack size in case the VM cares */
  10.         this.stackSize = stackSize;
  11.         /* Set thread ID */
  12.         this.tid = nextThreadID();
  13.     }
  14.    
复制代码
方法参数中的boolean inheritThreadLocals表示是否要继承/遗传。手动创建的线程,这里都会是true,代表要继承/遗传。
parent.inheritableThreadLocals != null 判断父线程的inheritableThreadLocals 属性是否为null (是否初始化了),如果不为null(父线程有需要遗传的本地变量),才发生遗传动作。
条件都满足则发生遗传,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )入参为父线程的遗传本地变量,也就是属性inheritableThreadLocals。
之后进入ThreadLocalMap的构造方法,开始构造子线程的inheritableThreadLocals 属性:
  1.         private ThreadLocalMap(ThreadLocalMap parentMap) {
  2.             Entry[] parentTable = parentMap.table;
  3.             int len = parentTable.length;
  4.             setThreshold(len);
  5.             table = new Entry[len];
  6.             for (Entry e : parentTable) {
  7.                 if (e != null) {
  8.                     @SuppressWarnings("unchecked")
  9.                     ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
  10.                     if (key != null) {
  11.                         Object value = key<strong>.childValue(e.value)</strong>;
  12.                         Entry c = new Entry(key, value);
  13.                         int h = key.threadLocalHashCode & (len - 1);
  14.                         while (table[h] != null)
  15.                             h = nextIndex(h, len);
  16.                         table[h] = c;
  17.                         size++;
  18.                     }
  19.                 }
  20.             }
  21.         }
复制代码
方法中就是遍历父线程的Map生成子线程自己的Map.Entry 了。
这里注意,在获取Entry的key 时,通过entry.get() 拿到父线程的Map 的key的弱引用,强制转化为ThreadLocal类型即可。
在获取value 值时,调用的是key的childValue()方法,也就是InheritableThreadLocal.childValue()中重写的方法,将父线程的value值转为子线程的value时。
这样子线程中:
1、map初始化好了;
2、entry元素也通过父线程都转移到子线程中了;
3、获取的遗传map,使用的都是遗传map了。(通过getMap()重写)
map的get set remove 等核心逻辑都直接使用父类的逻辑。(因为InheritableThreadLocal继承自ThreadLocal,并且没有重写这段逻辑)
总体上了来说,ThreadLocal,InheritableThreadLocal的实现都非常的优雅,不但很好的利用了对象的继承,保证用户在使用时无感知的发生了继承。其次用户在自定义自己的InheritableThreadLocal class时,(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )也只需要完成如何转化即可。非常值得我们自己在设计类结构和关系时参考这里的细节设计。
类关系整体的结构如下:
1.png

除此之外,我们也可以通过源码发现InheritableThreadLocal的一个特性,就是属性的遗传来自于父InheritableThreadLocal的全量属性,是不能根据某个线程自定义的。
并且在遗传时,子线程是在被new出实例时,就已经获得了此刻全部的属性,如果父线程后续调整了InheritableThreadLocal的范围,子线程是感知不到的。
来看这样一个例子即可:
  1.     public static void main(String[] args) {
  2.         ThreadLocal<String> tl1 = new InheritableThreadLocal<>();
  3.         ThreadLocal<String> tl2 = new InheritableThreadLocal<>();
  4.         ThreadLocal<String> tl3 = new InheritableThreadLocal<>();
  5.         tl1.set("t1");
  6.         tl2.set("t2");
  7.         Thread sonThread = new Thread(new Runnable() {
  8.             @Override
  9.             public void run() {
  10.                 String s1 = tl1.get();
  11.                 String s2 = tl2.get();
  12.                 String s3 = tl3.get();
  13.                 System.out.println("out:" + s1);
  14.                 System.out.println("out:" + s2);
  15.                 System.out.println("out:" + s3);
  16.             }
  17.         });
  18.         tl2.set("newTl2Value");
  19.         sonThread.start();
  20.     }
复制代码
输出如下,所有的值都是在new 子线程时就已经发生了,而并不是在子线程start之前:
  1. out:t1
  2. out:t2
  3. out:null
复制代码
 

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册