zoukankan      html  css  js  c++  java
  • 三种ThreadLocal,玩转线程变量保存与传递

    ThreadLocal

    ThreadLocal是jdk中自带的类,用于保存本线程专有的数据,从下面的代码大家可以很直接的看到它的作用

     private static ThreadLocal<String> sThreadLocal=new ThreadLocal<>();
    
        @Test
        void testThreadlocal() {
            //主线程
            sThreadLocal.set("这是在主线程中");
            System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
            //线程a
            new Thread(new Runnable() {
                public void run() {
                    sThreadLocal.set("这是在线程a中");
                    System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
                }
            },"线程a").start();
            //线程b
            new Thread(new Runnable() {
                public void run() {
                    sThreadLocal.set("这是在线程b中");
                    System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
                }
            },"线程b").start();
            
        }
    

    运行结果:
    ​​​​运行结果
    很明显可以看到,sThreadLocal变量只有一份,为字符串类型,但是主线程和ab两个子线程打印出来的sThreadLocal变量值是不一样的,这就是ThreadLocal命名的含义,创建专属于线程的变量数据。
    为什么能达到这种效果呢,其实原理也很简单,我们点进去ThreadLocal的set方法里面看看。

    public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    在这个方法里面可以看到一个叫ThreadLocalMap的变量,与Hashmap类似,我们的value就是设置到这里面的,而再次点进去发现getMap方法是从Thread类本身去拿的,那么为什么ThreadLocal变量和线程绑定也就很好理解了,因为保存ThreadLocal的ThreadLocalMap就是线程的一个变量。

        Thread.class:
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    

    ThreadLocal一般配合tomcat的一个请求一个业务线程的模型使用,保存类似于用户信息等全局都可能用到的内容,避免变量透传。

    InheritableThreadLocal

    看名字就知道,这个类是用于父子线程的变量传递,我们只需在上面测试代码上略作修改

      private static InheritableThreadLocal<String> sThreadLocal=new InheritableThreadLocal<>();
    
        @Test
        void testThreadlocal() {
            //主线程
            sThreadLocal.set("这是在主线程中");
            System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
            //线程a
            new Thread(new Runnable() {
                public void run() {
                    System.out.println("线程名字:"+Thread.currentThread().getName()+"---"+sThreadLocal.get());
                }
            },"线程a").start();
            
        }
    

    运行结果:

    可以看到我们新建线程后明明没有任何set sThreadLocal的行为,但依然能获取到主线程(父线程)的sThreadLocal变量,这就是InheritableThreadLocal和 ThreadLocal区别所在,变量可以由父线程传给子线程。

    TransmittableThreadLocal

    实际开发中InheritableThreadLocal一般很少用到,原因很简单,正式的开发中没人会直接new一个线程,这样会导致线程难以管理和回收,绝大多数项目在涉及到多线程的时候都会使用线程池,但是这也带来一个问题,线程池中的线程和业务主线程没有任何从属关系,那是不是ThreadLocal的变量只能显式透传过去了呢,这时候我们可以使用阿里开源的组件transmittable-thread-local 解决。

    //直接使用包装好的线程池即可
    static ExecutorService pool =  TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(3));
    

    如果是使用spring异步注解@Aynsc也是同理,只需自定义异步线程池为ttlExecutor即可
    TransmittableThreadLocal原理也很简单,关键在于自定义的TtlRunnable完成了转存ThreadLocal动作

     @Override
        public void run() {
            final Object captured = capturedRef.get();
            if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
                throw new IllegalStateException("TTL value reference is released after run!");
            }
            //这一步就是转存变量动作
            final Object backup = replay(captured);
            try {
                runnable.run();
            } finally {
                restore(backup);
            }
        }
    
    //实际执行转存方法
    private static HashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> captured) {
                final HashMap<ThreadLocal<Object>, Object> backup = new HashMap<ThreadLocal<Object>, Object>();
    
                for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
                    final ThreadLocal<Object> threadLocal = entry.getKey();
                    backup.put(threadLocal, threadLocal.get());
    
                    final Object value = entry.getValue();
                    if (value == threadLocalClearMark) threadLocal.remove();
                    else threadLocal.set(value);
                }
    
                return backup;
            }
    

  • 相关阅读:
    克罗内克符号
    子进程和线程
    力扣:数学问题
    iconv装换文件编码格式
    mac 上iterm终端显示中文为乱码解决方案
    Android启动时间测试方法
    Centos4.3安装MySQL-python-1.2.3,出现error: command 'gcc' failed with exit status 1
    java mail发送邮件
    Java通过socket实现smtp协议发送邮件
    vim解决中文显示乱码问题
  • 原文地址:https://www.cnblogs.com/CodeSpike/p/15321090.html
Copyright © 2011-2022 走看看