JobPlus知识库 IT 软件开发 文章
再谈ThreadLocal

一、引言

众所周知,ThreadLocal是java开发中不可或缺的重要类,而且它在多线程环境下能发挥举足轻重的作用。它的存储结构类似于Map<Thread,Object>,就是以当前线程作为Key的一个Map.所以不同线程之间能做到资源独立,不存在并发访问的问题。由于市面上很多讲ThreadLocal源码的文章,我这里就不再赘述了,我相信绝大部分的童鞋随便花点时间就可以理解。

二、ThreadLocal的使用场景

  1. spring的事务传播特性
    将事务沿着当前线程进行传递.
  2. 稀有资源控制访问
    比如数据库连接、分页等等都可以使用ThreadLocal来做。
  3. 分布式跟踪系统
    需要检测线程经过的所有链路的耗时情况。

三、ThreadLocal的弊端

  1. 跨线程的问题。
    由于ThreadLocal具有线程独立副本,线程之间互不干扰。所以它必然就处理不了跨线程的问题。现在考虑这么一个需求,我们先看一个spring 服务代码示例:

   public Integer doHandle(){          Integer resultA = methodA();          Integer resultB = longTimeMethod();//耗时服务          Integer resultC = methodC();          Integer resultAll = mergeResult(resultA,resultB,resultC);//合并结果集          return resultAll;    }已知:

doHandle()是一个事务方法。

需求:

longTimeMethod()方法是一个耗时方法,所以要考虑把它作成异步服务.例如:

   new Thread(new Callable<Integer>(){          public Integer call(){                return  longTimeMethod();          }      }).start();

但是这样做就会出现一个问题:
因为spring的事务是利用ThreadLocal来做的,如果在事务方法中单独再起一个线程去运行另外一个服务,则另外一个服务就获取不到原来的事务,所以只能把longTimeMethod()单独作成一个事务服务来运行.

四、谈谈InheritableThreadLocal

我们前面已经讲到ThreadLocal有跨进程的问题,也就是线程A的线程独立副本对于新创建的线程B不可见。但是为了把线程A的独立副本传递给子线程来实现更复杂的需求,JDK就实现了InheritableThreadLocal。InheritableThreadLocal继承自ThreadLocal,覆盖了ThreadLocal的getMap和createMap方法。它又是怎么实现线程之间数据的传递的呢?下面我们来说说。

  1. 先来看下Thread类中的成员变量

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

竟然Thread还有另外一个和threadLocals类型一样的变量,是不是很惊讶?竟然都没有关注过。那inheritableThreadLocals是在哪里被赋值的呢?我们来看看

原来在new Thread()的时候,会将当前线程的inheritableThreadLocals传递给新创建线程的inheritableThreadLocals变量,即完成了一次"华丽的拷贝"。

  1. 再来看看InheritableThreadLocal中的几个方法

   protected T childValue(T parentValue) {        return parentValue;    }    ThreadLocalMap getMap(Thread t) {       return t.inheritableThreadLocals;    }    void createMap(Thread t, T firstValue) {        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);    }

所以我们在子线程中调用get()方法的时候,会从子线程中的inheritableThreadLocals取值。而inheritableThreadLocals就是父线程在new Thread()的时候传递给子线程,一切都恍然大悟!!!难怪子线程可以获取到父线程设置的属性值。下面看一个简单实例来证实一下:

final InheritableThreadLocal<SpanId> inheritableThreadLocal = new InheritableThreadLocal<SpanId>();        inheritableThreadLocal.set(new SpanId("main thread"));        System.out.println(inheritableThreadLocal.get().name);        Runnable runnable = new Runnable() {            @Override            public void run() {                System.out.println("-----------");                                System.out.println("--->: " + inheritableThreadLocal.get().name);//父线程将属性值传递到子线程了                inheritableThreadLocal.set(new SpanId("inner thread"));                System.out.println(inheritableThreadLocal.get().name);                                System.out.println();                            }        };        ExecutorService executorService = Executors.newFixedThreadPool(10);//手动修改线程池大小,这里设置为10,保证是新创建的线程,而不是直接取池中的线程。        executorService.submit(runnable);        TimeUnit.SECONDS.sleep(1);                System.out.println("after sleep-> " + inheritableThreadLocal.get().name);                executorService.submit(runnable); //重新执行任务,又将main线程的inheritableThreadLocals属性值复制到子线程中。        TimeUnit.SECONDS.sleep(1);                System.out.println("########");        SpanId span = inheritableThreadLocal.get();        System.out.println("main-> " + span.name); 输出结果: main thread ----------- --->: main thread inner thread after sleep-> main thread ----------- --->: main thread //这里依旧是main thread,因为是 new Thread(),所以将值传递进来了inner thread ######## main-> main thread

将newFixedThreadPool中的线程数改成1再试一次。执行结果如下:

main thread ----------- --->: main thread inner thread after sleep-> main thread ----------- --->: inner thread//由于线程数固定为1,所以第二次执行任务时直接取池中的线程,不会实现独立数据副本的拷贝,故不能传递父线程的数据。只能打印原来线程的数据。inner thread ######## main-> main thread五、InheritableThreadLocal的弊端


如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!

¥ 打赏支持
108人赞 举报
分享到
用户评价(0)

暂无评价,你也可以发布评价哦:)

扫码APP

扫描使用APP

扫码使用

扫描使用小程序