多线程并发数据访问,确保数据安全至关重要,常用保证数据安全的方法有对代码synchronized锁、Lock锁,以及基于CAS的原子类,这些都是通过数据共享保障数据安全的,今天聊一聊另一种方案ThreadLocal线程副本,实现线程间的数据隔离,达到数据安全的目标。

一、ThreadLocal数据共享

       ThreadLocal是线程变量,ThreadLocal中填充的线程变量是当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程只可以访问自己内部的副本变量。

         翻看源码,ThreadLocal是当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadlocl,value),虽然不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独一无二的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量地址是一样的。

          每个线程都维护了自己的一个 TreadLocalMap

1、ThreadLocal数据隔离的案例


    public static class  Data{

        private final ThreadLocal<String>  threadLocalData  = ThreadLocal.withInitial(() -> "");

        public void  warpInfo(String prefix){
            // 给当前线程变量设置值
           threadLocalData.set(prefix+"-"+threadLocalData.get());
        }

        public  String  getInfo(){
            /// 获取当前线程变量
            return  threadLocalData.get();
        }

        public  void   clearInfo(){
            /// 变量使用完后一定要释放,原因有二:
            // 1、ThreadLocalMap的key是WeakReference<ThreadLocal<?>> 弱引用,当发生gc时候,key被回收,而value是强引用为无法被回收,造成整个threadLocalMap变量无法回收,最终引起内存泄露;
            // 2、在使用线程池的时候,线程复用,或造成线程变量覆盖,影响正常业务
            threadLocalData.remove();
        }
    }
    private final static   ExecutorService POOL = Executors.newFixedThreadPool(3);

    public  static void main(String[] args) {
        Data data = new Data();
        for(int i=0;i< 8;i++){
            POOL.execute(() -> {
                try {
                    data.warpInfo("subThread");
                    System.out.println(Thread.currentThread().getName()+"    data=> "+data.getInfo());
                } finally {
                    data.clearInfo();
                }
            });
        }

      执行结果:

2、ThreadLocal父子线程数据传递问题

       先演示现象

   Data data = new Data();
   data.warpInfo("mainThread");
   POOL.execute(()->{
      System.out.println("111->info= "+ data.getInfo());
      data.warpInfo("subThread");
      System.out.println("222-info= "+data.getInfo());
   });

      不出意外,第一个输出是mainThread-,第二个输出是mainThread-subThread-,但是实际输出确出现意外了

        为什么会出现这个现象,这是因为在子线程和主线程之间是无法传递数据的,这更加印证了ThreadLocal具有线程隔离的作用。但是有时候确实存在需求需要线程间传递数据,并且还要保证线程安全。为了解决这一问题,官方推出了InheritableThreadLocal专门应对此种情况。

二、InheritableThreadLocal主线程子线程间数据传递

         InheritableThreadLocal是ThreadLocal的子类,通过Thread类维护inheritableThreadLocals变量,在createInheritedMap的时候获取了读线程的变量值

        改造Data类,直接将ThreadLocal替换成InheritableThreadLocal

 private final  InheritableThreadLocal<String>  threadLocalData =   new InheritableThreadLocal<>();

        执行结果,可以看到主线线程的变量值在子线程中可以正常取到。

三、TransmittableThreadLocal线程池线程之间数据传递

         不乏存在线程池内部线程之间需要实现数据传递,针对此种需求,阿里的开源工具TransmittableThreadLocal可以有效解决此类问题。

          先看下问题现象

        InheritableThreadLocal<String>  threadLocal = new InheritableThreadLocal<>();
        threadLocal.set("main-thread");
        System.out.println(Thread.currentThread().getName()+" get local data  start ===> "+threadLocal.get());
        ExecutorService pool = Executors.newFixedThreadPool(3);
        pool.execute(()-> System.out.println(Thread.currentThread().getName()+" get local data ===> "+threadLocal.get()));
        sleep(1);
        threadLocal.set("main-thread-new");
        System.out.println(Thread.currentThread().getName()+" get local data end ===> "+threadLocal.get());
        pool.execute(()-> System.out.println(Thread.currentThread().getName()+" get local data ===> "+threadLocal.get()));
        sleep(Integer.MAX_VALUE);

        线程池里面线程执行第二次打印的结果如果是main-thread-new,那么InheritableThreadLocal

或许具备线程池中传递线程变量的能力,看下执行结果并不理想

         线程池中第二次获取主线程修改后的变量是不可见的。解决这个问题只需要稍微改造,引入

TransmittableThreadLocal即可:

        TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();
        threadLocal.set("main-thread");
        System.out.println(Thread.currentThread().getName()+" get local data  start ===> "+threadLocal.get());
        ExecutorService  pool = TtlExecutors.getTtlExecutorService( Executors.newFixedThreadPool(1));
        pool.execute(()-> System.out.println(Thread.currentThread().getName()+" get local data ===> "+threadLocal.get()));
        sleep(1);
        threadLocal.set("main-thread-new");
        System.out.println(Thread.currentThread().getName()+" get local data end ===> "+threadLocal.get());
        pool.execute(()-> System.out.println(Thread.currentThread().getName()+" get local data ===> "+threadLocal.get()));

        sleep(Integer.MAX_VALUE);

        至此完美解决线程变量线程池线程传递。 

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐