zoukankan      html  css  js  c++  java
  • InheritableThreadLocal——父线程传递本地变量到子线程的解决方式及分析

    上一个博客提到ThreadLocal变量的基本使用方式,可以看出ThreadLocal是相对于每一个线程自己使用的本地变量,但是在实际的开发中,有这样的一种需求:父线程生成的变量需要传递到子线程中进行使用,那么在使用ThreadLocal似乎就解决不了这个问题,难道这个业务就没办法使用这个本地变量了吗?答案肯定是否定的,ThreadLocal有一个子类InheritableThreadLocal就是为了解决这个问题而产生的,使用这个变量就可以轻松的在子线程中依旧使用父线程中的本地变量。

     

    --> InheritableThreadLocal基本的使用和分析:

    如图上的代码所示,创建一个InheritableThreadLocal变量,并在main主线程中对该变量进行初始化,然后新建一个子线程直接获取刚刚初始化的变量值,代码执行的实际效果如下:

    运行的结果可以看出,使用该变量子线程可以使用父线程中的本地变量值,那么具体的原因是什么呢?还是从源码一看究竟。

     

    --> InheritableThreadLocal源码浅析:

    首先看一下该类的目录结构,继承自ThreadLocal,并且重写了父类的方法:createMap(),getMap(),childValue();

     

    其次当主线程中对该变量进行set操作的时候,和ThreadLocal一样会初始化一个ThreadLocalMap对实际的变量值进行存储,以ThreadLocal为key,值为value,如果有多个ThreadLocal变量也都是存储在这个Map中。该Map使用的是HashMap的原理进行数据的存储,但是和ThreadLocal有一点差别,因为其覆写了createMap的方法。

     

     

    再将目光转移到线程Thread类中,可以看出Thread类维护了两个成员变量,ThreadLocal以及InheritableThreadLocal,数据类型都是ThreadLocalMap.这也就解释了为什么这个变量是线程私有的。但是如果要知道为什么父子线程的变量传递,那就继续看一下源码。当我们在主线程中开一个新的子线程的时候,开始会new一个新的Thread,具体的实现也就是在这里。

    经过调用init方法可以看出,方法中存在对InheritableThreadLocal的操作。获取的父线程也就是当前实际开辟线程的主线程。

    当父线程中的inheritableThreadLocal被赋值时,会将当前线程的inheritableThreadLocal变量进行createInheritedMap(),看一下这个方法的具体实现,它会继续调用ThreadLocalMap(parentMap),主要的目的是父线程的变量值赋值给子线程。这里直接改变的是Entry[],因为ThreadLocalMap只是一个类名,具体数据存储和操作是使用内部的数组搭配Hash算法和Entry内部类实现。

     

    代码看到这里,对于为什么父线程的InheritableThreadLocal变量可以传递给子线程的原因应该已经清晰了。

     

    --> InheritableThreadLocal和线程池搭配使用存在的问题:

    首先创建一个线程池,设置其固定大小为1,调用这个线程池两次,在此之前分别对主线程中的InheritableThreadLocal进行赋值操作,观察运行的结果。

     

     

    两次调用获取的值是一开始赋值的值,因为线程池中是缓存使用过的线程,当线程被重复调用的时候并没有再重新初始化init()线程,而是直接使用已经创建过的线程,所以这里的值并不会被再次操作。因为实际的项目中线程池的使用频率非常高,每一次从线程池中取出线程不能够直接使用之前缓存的变量,所以要解决这一个问题,网上大部分是推荐使用alibaba的开源项目transmittable-thread-local。具体可以自行了解,但是笔者认为需要弄清楚线程池和主线程以及本地变量直接的详细关系才可以更好的对这个项目有所理解。

     

    --> 线程池扩展源码阅读

    从构造线程池到使用线程池的过程:

    1.创建一个线程池,其内部是对线程池基本的参数进行封装。

     

     

    如上的构造方法可以看出,实际对线程进行初始化的接口是ThreadFactory,当线程池执行submit或者execute操作的时候,会对线程进行实际的构造,源码选用submit(Runable)进行阅读。

    执行submit的时候首先进入方法newTaskFor();

    实际是通过FutureTask对任务类进行封装,并且初始化的状态是NEW,代码跟进如下:

     

    其次调用的是execute()方法:

    如上的方法中,ctl相当于一个记录当前活跃线程数量的类,在ThreadLocalExcutor中被初始化,

    所以在execute的代码中,首先判断正在执行任务的线程数量是否小于设置的线程数,来决定是否创建新的线程或者等待被执行。如果可以添加通过addWorker()进行线程的创建和添加,这也就是主线程和当前线程池中的线程传递本地变量的地方,因为这里会新建一个Thread。

    详细的多线程源码分析笔者后续会继续准备。。。
    ————————————————
    原文链接:https://blog.csdn.net/hewenbo111/article/details/80487252

  • 相关阅读:
    centos 用户管理
    rsync 实验
    文件共享和传输
    PAT 1109 Group Photo
    PAT 1108 Finding Average
    PAT 1107 Social Clusters
    PAT 1106 Lowest Price in Supply Chain
    PAT 1105 Spiral Matrix
    PAT 1104 Sum of Number Segments
    PAT 1103 Integer Factorization
  • 原文地址:https://www.cnblogs.com/tiancai/p/15234005.html
Copyright © 2011-2022 走看看