ThreadLocal学习笔记(二)

时间:2022-07-24
本文章向大家介绍ThreadLocal学习笔记(二),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

上一篇我们基本了解了ThreadLocal的大致过程,也就是Thread的局部变量ThreadLocalMap的相关操作。但是在Thread类中我们看到inheritableThreadLocals变量。而且类型与上期说的ThreadLcoalMap一样。按理说说定义一个ThreadLocalMap就可以,这里为什么要定义两个?

那么我们看看这里这个变量是如何初始化的,一般的类的初始化会在构造方法中进行初始化。我们就看看初始化方法做了哪些工作。

        private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {
      //这里的inheritThreadLocals表示是否进行同步父子线程的ThreadLocalMap,默认传递true
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
    //代码执行到这里还没有创建子线程,所以这里拿到的是父线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
    //判断父线程的inheritableThreadLocals是否为空,不为空的就进行拷贝
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //将父线程的ThreadLocalMap同步过来
            this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        this.stackSize = stackSize;
        tid = nextThreadID();
    }
  //同步父线程中的变量
  private ThreadLocalMap(ThreadLocalMap parentMap) {
          //拿到父线程的entity数组
            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")
          //这里的Key为ThreadLocal类
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
              //调用父类的childValue方法
                        Object value = key.childValue(e.value);
            //创建一个洗呢Entry元素
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
    }
  //这里看到childValue是需要开发者自己去定义的。
  T childValue(T parentValue) {
        throw new UnsupportedOperationException();
  }
  

代码看到这里,就比较疑惑了,这里有复制拷贝的操作。但是赋值的操作在哪里??真的是一脸问号。找了半天没有找到,这时候却似需要怀疑一下ThreadLcoalMap定义两个究竟是什么意思。甚至我们之前对ThreadLocal的分析都是有问题的。问题就在于这个inheritableThreadLocals是在何时被赋值的。但是可笑的是想了半天都没有想到。而且这个肯定是和Thread类中的两个ThreadLocalMap挂钩的。

无意中,发现了一个类居然就叫做InheritableThreadLocal!

而且就继承了ThreadLocal,根据java父子类的关系。我们就知道如果子类和父类方法相同,都是走子类的方法。我们看看InheritableThreadLocal都有哪些方法。

在ThreadLocal中

看到这里是不是有种恍然大悟的感觉,所谓的childValue要子类去扩展是啥意思。InheritableThreadLocal就是最明显的子类。也就是说如果我们项目中定义的是InheritableThreadLocal,那么底层的getMap就走的InheritableThreadLocal子类的getMap,也就是返回的是inheritableThreadLocals,也就是说ThreadLocalMap就不用了,所有的数据存储和操作都是inheritableThreadLocals。也就是说使用了InheritableThreadLocal的话,就自然具有父线程局部变量inheritableThreadLocals向子线程局部变量的拷贝。

问题是能不能项目同时使用ThreadLocal和InheritableThreadLocal?我觉得是可以的,因为这两者都有自己的存储容器,而且互相不干扰。而且getMap方法其实在写代码时候就已经决定了究竟走的那个map。所以应该是没有问题的。为了验证上述分析,这里测试一下。看看是否符合预期。

public class User {

    private String name;

    private String phone;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + ''' +
                ", phone='" + phone + ''' +
                '}';
    }
}

public class TaskThreadLocalTask implements Runnable{


    @Override
    public void run() {

        //打印threadLocal中的值,按理说是不会被继承的。所以这里的打印为空
        User user=MyThreadLocal.get();
        String string="";
        if (null!=user){
            string=user.toString();
        }
        System.out.println("当前线程:"+Thread.currentThread().getName()+"---信息"+string);
        //打印可继承的ThreadLocal的值,这里因为采用的是继承的,所以会打印主进程的参数
        System.out.println("当前线程:"+Thread.currentThread().getName()+"---信息"+MyThreadLocal.getInherit().toString());
    }
}

public class TestThreadLocal {
    public static void main(String[] args) {
        User user=new User();
        user.setName("tianjl");
        user.setPhone("123123");
        System.out.println("主线程"+Thread.currentThread().getName()+"--消息体:"+user.toString());
        MyThreadLocal.set(user);
        MyThreadLocal.setInherit(user);
        ThreadPoolExecutor executor= (ThreadPoolExecutor) Executors.newFixedThreadPool(5);
        for (int i=0;i<5;i++){
            executor.execute(new TaskThreadLocalTask());
        }
        //这里的shutdown只是停止线程池添加线程,并不会停止正在运行的线程。
        executor.shutdown();
    }
}

通过上述实验,证明了ThreadLocal是父子线程隔离的,InheritableThreadLocal是可以继承的。而且在项目中两者是可以并存的。

这里测试使用的是线程池,我们知道线程池在没有任务的时候是一直自旋的状态。所以需要在任务执行结束之后关闭线程池。这里调用executor.execute方法执行任务。