前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊一聊线程变量绑定之InheritableThreadLocal

聊一聊线程变量绑定之InheritableThreadLocal

作者头像
山行AI
发布2019-12-19 11:30:49
8260
发布2019-12-19 11:30:49
举报
文章被收录于专栏:山行AI

通过上一节我们知道,ThreadLocal 可以用于线程变量绑定和隔离,但是却无法做到服务调用链路很长时,需要做链路追踪时,子线程无法获取到父线程中的共享变量的情况,本节的 InheritableThreadLocal 就是用来解决这个问题的。

示例

代码语言:javascript
复制
@Test
public void testInheritableThreadLocal() throws InterruptedException {
    InheritableThreadLocal threadLocal = new InheritableThreadLocal();
    threadLocal.set("simple thread local in main thread!");
    Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("inner thread:" + threadLocal.get());
            threadLocal.set("simple ThreadLocal in thread!");
            System.out.println("inner thread:" + threadLocal.get());
        }
    });
    thread.start();
    thread.join();
    System.out.println(threadLocal.get());
}

输出结果为:

代码语言:javascript
复制
inner thread:simple thread local in main thread!
inner thread:simple ThreadLocal in thread!
simple thread local in main thread!

可以看到,在子线程中拿到了父线程的 threadLocal 变量的值。

源码

我们继续按照分析 ThreadLocal 源码的思路来分析一下 InheritableThreadLocal 变量。

Thread 构造方法

代码语言:javascript
复制
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
        init(g, target, name, stackSize, null);
    }

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
    .....................
     if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ..............
}

在 Thread 中有一个 inheritableThreadLocals 变量,它的值是在构造方法中赋值的,会将父 Thread 的 inheritableThreadLocals 变量传入到当前子线程的中,并通过 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)方法创建子线程的 inheritableThreadLocals。

ThreadLocal.createInheritedMap

代码语言:javascript
复制
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

     private ThreadLocalMap(ThreadLocalMap parentMap) {
            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")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

会将父线程的 ThreadLocalMap 中的 table 中的 Entry 拷到子线程的 ThreadLocalMap 中。

在这里我们尤其需要关注的是这一行:

代码语言:javascript
复制
Object value = key.childValue(e.value)

这在 InheritableThreadLocal 中有对应的实现:

代码语言:javascript
复制
protected T childValue(T parentValue) {
       return parentValue;
   }

直接返回的是父线程中的 value。

其他方法

getMap

代码语言:javascript
复制
ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

返回的是当前线程的 inheritableThreadLocals 变量。

createMap

代码语言:javascript
复制
void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }

创建 ThreadLocalMap 的时候也是用的 inheritableThreadLocals 引用。

InheritableThreadLocal 主要用于子线程创建时,需要自动继承父线程的 ThreadLocal 变量,方便必要信息的进一步传递。

问题

我们看下下面这两个示例: 示例一:

代码语言:javascript
复制
@Test
    public void testMultiThreadWithoutPool(){
        InheritableThreadLocal threadLocal = new InheritableThreadLocal();
        IntStream.range(0,10).forEach(i -> {
            threadLocal.set(i);
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }).start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

输出结果:

代码语言:javascript
复制
Thread-0:0
Thread-1:1
Thread-2:2
Thread-3:3
Thread-4:4
Thread-5:5
Thread-6:6
Thread-7:7
Thread-8:8
Thread-9:9

示例二:

代码语言:javascript
复制
private ExecutorService service = Executors.newFixedThreadPool(1);

    @Test
    public void testInheritableByThreadPool(){
        InheritableThreadLocal threadLocal = new InheritableThreadLocal();
        IntStream.range(0,10).forEach(i -> {
            System.out.println(i);
            threadLocal.set(i);
            service.submit(() -> {
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            });
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

输出结果:

代码语言:javascript
复制
0
pool-1-thread-1:0
1
pool-1-thread-1:0
2
pool-1-thread-1:0
3
pool-1-thread-1:0
4
pool-1-thread-1:0
5
pool-1-thread-1:0
6
pool-1-thread-1:0
7
pool-1-thread-1:0
8
pool-1-thread-1:0
9
pool-1-thread-1:0

对比两者的结果可以发现,同样的 set 操作,结果大不相同。这是因为示例一是每次 new Thread 的操作都会将父线程的 ThreadLocal 变量传入子线程中,示例二是线程池的操作,线程只会初始化一次,子线程是取不到父线程变量的实时变动的。关于这个问题,下篇文章中将要讲的 TransmittableThreadLocal 可以完美解决。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开发架构二三事 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 示例
  • 源码
    • Thread 构造方法
      • ThreadLocal.createInheritedMap
        • 其他方法
          • getMap
          • createMap
      • 问题
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档