最近使用InheritableThreadLocal遇到一些问题,这里总结一下。

Thread Local

ThreadLocal是绑定到线程上的上下文,他可以让你很方便的在同一个线程里进行上下文数据传递和共享,线程与线程之间的ThreadLocal是相互不可见的。

下面是一个简单的例子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
public class Test {


    public static void main(String[] args) throws Exception {

        AService aService = new AService();
        BService bService = new BService();

        new Thread(() -> {

            // 初始化线程上下文
            ThreadLocalContext.initContext();

            // 操作上下文
            aService.genNum();

            // 获取上下文信息
            bService.showNum();
        }).start();

        TimeUnit.SECONDS.sleep(5);
    }

    /**
     * 共享上下文
     */
    static class Context {

        private Integer num;

        public Integer getNum() {
            return num;
        }

        public void setNum(Integer num) {
            this.num = num;
        }
    }

    /**
     * 线程上下文
     */
    static class ThreadLocalContext {

        public static ThreadLocal<Context> threadLocal;

        public static void initContext() {
            threadLocal = new ThreadLocal<>();
        }

        public static void putContext(Context context) {
            threadLocal.set(context);
        }

        public static Context getContext() {
            return threadLocal.get();
        }
    }


    static class AService {

        public void genNum() {
            Context context = ThreadLocalContext.getContext();
            if (context == null) {
                context = new Context();
                context.setNum(0);
            }

            context.setNum(context.getNum() + 1);
            ThreadLocalContext.putContext(context);
        }
    }

    static class BService {
        public void showNum() {
            System.out.println(ThreadLocalContext.getContext().getNum());
        }
    }
}

从上面可以看到在多个service中通过Thread Local传递共享数据非常便利,不用显示的在现有service进行显示传递。 这在一些场景中非常有用,例如对老旧系统的功能修改,可以做到不破坏原有服务接口签名。 还有全链路埋点(分析统计、监控)可以做到解藕。

InheritableThreadLocal

简单来讲InheritableThreadLocalThreadLocal的增强版,它带了一项新的能力 ,子线程会拷贝父线程中ThreadLocal

之前用ThreadLocal我们都是这样做的:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
public class Test {


    public static void main(String[] args) throws Exception {

        AService aService = new AService();
        BService bService = new BService();
        CService cService = new CService();

        // 初始化主线程上下文
        ThreadLocalContext.initContext();

        // 主线程中的逻辑,会操作主线程上下文
        cService.setNum();

        // 获取主线程中的上下文
        Context context = ThreadLocalContext.getContext();

        new Thread(() -> {

            // 子线上下文直接用主线程中的上下文创建
            ThreadLocalContext.setContext(context);

            // 操作上下文
            aService.genNum();

            // 获取上下文信息
            bService.showNum();
        }).start();

        TimeUnit.SECONDS.sleep(5);
    }

    /**
     * 共享上下文
     */
    static class Context {

        private Integer num;

        public Integer getNum() {
            return num;
        }

        public void setNum(Integer num) {
            this.num = num;
        }
    }

    /**
     * 线程上下文
     */
    static class ThreadLocalContext {

        public static ThreadLocal<Context> threadLocal;

        public static void initContext() {
            threadLocal = new ThreadLocal<>();
        }

        public static void setContext(Context context) {
            threadLocal = new ThreadLocal<>();
            threadLocal.set(context);
        }

        public static void putContext(Context context) {
            threadLocal.set(context);
        }


        public static Context getContext() {
            return threadLocal.get();
        }
    }


    static class AService {

        public void genNum() {
            Context context = ThreadLocalContext.getContext();
            if (context == null) {
                context = new Context();
                context.setNum(0);
            }

            context.setNum(context.getNum() + 1);
            ThreadLocalContext.putContext(context);
        }
    }

    static class BService {
        public void showNum() {
            System.out.println(ThreadLocalContext.getContext().getNum());
        }
    }

    static class CService {
        public void setNum() {
            Context context = new Context();
            context.setNum(1);
            ThreadLocalContext.putContext(context);
        }
    }

}

需要自已手动将主线程中的上下文传递到子线程中的ThreadLocal中。


下面我们来看看使用InheritableThreadLocal

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
public class Test {


    public static void main(String[] args) throws Exception {

        AService aService = new AService();
        BService bService = new BService();
        CService cService = new CService();

        // 初始化主线程上下文
        ThreadLocalContext.initContext();

        // 主线程中的逻辑,会操作主线程上下文
        cService.setNum();

        // 获取主线程中的上下文
        // Context context = ThreadLocalContext.getContext();

        new Thread(() -> {

            // 子线上下文直接用主线程中的上下文创建
            // ThreadLocalContext.setContext(context);

            // 操作上下文
            aService.genNum();

            // 获取上下文信息
            bService.showNum();
        }).start();

        TimeUnit.SECONDS.sleep(5);
    }

    /**
     * 共享上下文
     */
    static class Context {

        private Integer num;

        public Integer getNum() {
            return num;
        }

        public void setNum(Integer num) {
            this.num = num;
        }
    }

    /**
     * 线程上下文
     */
    static class ThreadLocalContext {

        public static InheritableThreadLocal<Context> threadLocal;

        public static void initContext() {
            threadLocal = new InheritableThreadLocal<>();
        }


        public static void setContext(Context context) {
            threadLocal = new InheritableThreadLocal<>();
            threadLocal.set(context);
        }

        public static void putContext(Context context) {
            threadLocal.set(context);
        }


        public static Context getContext() {
            return threadLocal.get();
        }
    }


    static class AService {

        public void genNum() {
            Context context = ThreadLocalContext.getContext();
            if (context == null) {
                context = new Context();
                context.setNum(0);
            }

            context.setNum(context.getNum() + 1);
            ThreadLocalContext.putContext(context);
        }
    }

    static class BService {
        public void showNum() {
            System.out.println(ThreadLocalContext.getContext().getNum());
        }
    }

    static class CService {
        public void setNum() {
            Context context = new Context();
            context.setNum(1);
            ThreadLocalContext.putContext(context);
        }
    }

}

可以看到主动转递上下文的过程省去了,InheritableThreadLocal会自动搞定。

问题

InheritableThreadLocal看起来很美,但实际使用下来发现了几个问题:

  1. 拷贝ThreadLocal是在子线程创建的时候,所以线程池中ThreadLocal后续不拷贝主线程的ThreadLocal,因为线程池中大多数情况下线程是复用的,重新建的情况很少。
  2. 当初创建子线程的主线程如果清除ThreadLocal子线程中的ThreadLocal也会被清除。

下面是验证代码:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
public class Test {

    static ExecutorService executorService = Executors.newFixedThreadPool(1);

    static AService aService = new AService();
    static BService bService = new BService();
    static CService cService = new CService();


    public static void main(String[] args) throws Throwable {

        // 第一次执行
        test();
        TimeUnit.SECONDS.sleep(1);
        
        // 第二次执行
        test();
        TimeUnit.SECONDS.sleep(1);

    }

    private static void test() {
        
        new Thread(() -> {

            // 初始化主线程上下文
            ThreadLocalContext.initContext();

            // 主线程中的逻辑,会操作主线程上下文
            cService.setNum();

            executorService.submit(() -> {


                // 操作上下文
                aService.genNum();

                // 获取上下文信息
                bService.showNum();
            });

            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }

            ThreadLocalContext.clear();


        }).start();

    }

    /**
     * 共享上下文
     */
    static class Context {

        private Integer num;

        public Integer getNum() {
            return num;
        }

        public void setNum(Integer num) {
            this.num = num;
        }
    }

    /**
     * 线程上下文
     */
    static class ThreadLocalContext {

        public static InheritableThreadLocal<Context> threadLocal;

        public static void initContext() {
            threadLocal = new InheritableThreadLocal<>();
        }


        public static void setContext(Context context) {
            threadLocal = new InheritableThreadLocal<>();
            threadLocal.set(context);
        }

        public static void putContext(Context context) {
            threadLocal.set(context);
        }


        public static Context getContext() {
            return threadLocal.get();
        }

        public static void clear() {
            threadLocal.remove();
        }
    }


    static class AService {

        public void genNum() {
            Context context = ThreadLocalContext.getContext();
            if (context == null) {
                context = new Context();
                context.setNum(0);
            }

            context.setNum(context.getNum() + 1);
            ThreadLocalContext.putContext(context);
        }
    }

    static class BService {
        public void showNum() {
            System.out.println(ThreadLocalContext.getContext().getNum());
        }
    }

    static class CService {
        public void setNum() {
            Context context = new Context();
            context.setNum(1);
            ThreadLocalContext.putContext(context);
        }
    }

}

总结

  1. 总是优先使用ThreadLocal
  2. 在使用线程池的情况下应避免使用InheritableThreadLocal