2 ThreadLocal-InheritableThreadLocal

InheritableThreadLocal

ThreadLocal线程间的问题:ThreadLocal无法在父子线程间进行传递。例如:主线程设置ThreadLocal,并且创建了新线程,此时新线程访问主线程设置的ThreadLocal,返回null。在线程池中更为突出,因为线程池中的业务线程是复用的,每个任务提交时,都有不同的ThreadLocal。

为了解决ThreadLocal在父子线程中的传递问题,应运而生了InheritableThreadLocal。ThreadLocal不能解决父子线程间的传递,那么另起个变量InheritableThreadLocal,创建新线程时,复制父线程的InheritableThreadLocal,通过新变量解决上述问题。

存储位置

 /* 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;

inheritableThreadLocals和threadLocals,都存储在Thread线程中,只不过inheritableThreadLocals可以继承。

数据结构

查看InheritableThreadLocal就是继承自ThreadLocal,只不过修改了getMap/createMap方法,即返回不同的存储位置。

使用示例

使用范例:

public static void main(String[] args) {
        InheritableThreadLocal<String> itl = new InheritableThreadLocal<>();
        itl.set("age");
        System.out.println(Thread.currentThread().getName() + ":" + itl.get());
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + itl.get());
                itl.set("inheritable");
                System.out.println(Thread.currentThread().getName() + ":" + itl.get());

            }
        }).start();

        for (int i = 0; i < 100000; i++) {

        }
        System.out.println(Thread.currentThread().getName() + ":" + itl.get());
        itl.set("address");
        System.out.println(Thread.currentThread().getName() + ":" + itl.get());
    }
输出:
main:age
Thread-0:age
Thread-0:inheritable
main:age
main:address

通过上述范例,InheritableThreadLocal实现了ThreadLocal在线程中的传递,当然也存在一些缺点。

生效原因

创建线程通过new Thread方法,而方法的底层还是调用init方法,看看init方法。

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //省略一些无关代码
        //parent=主线程/父线程
        Thread parent = currentThread();
        //省略无关代码
        //父线程的inheritableThreadLocals不为空,copy
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        
    }

//createInheritableMap

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

//new ThreadLocalMap(parentMap) 就是把ThreadLocalMap copy一份
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++;
                    }
                }
            }
        }

生效的原因:子线程主动从父线程copy一份InheritableThreadLocal,这样就保证了上下文的传递。

缺点

InheritableThreadLocal也存在一些缺陷:

  1. copy是一次性的,只有子线程创建时,才会copy父线程,并且只copy因此。因此创建完子线程后,主线程添加的ThreadLocal,子线程获取仍然为null。
  2. 子线程的InheritableThreadLocal的各种操作,例如:set值,remove值,对主线程都没有影响。
  3. 子线程copy主线程的InheritableThreadLocal是浅拷贝,如果子线程/主线程,针对value的修改,双方都可以感知。优点:可以把通过value的变动,实现线程之间的互通。
    缺点:一方对value的修改,可能会影响另一方。
  4. InheritableThreadLocal对线程池不支持。因为子线程的copy是一次性的,因此当创建池内线程池,会从业务线程copy一份InheritableThreadLocal,但是当池内线程执行其他业务时,就不会copy该业务提交时,业务线程的InheritableThreadLocal,同时可能因为第一次的copy导致后续的InheritableThreadLocal的污染。
public class InheritableThreadLocalDemo {
    private static ExecutorService executorService = Executors.newFixedThreadPool(1);
    private static InheritableThreadLocal<String> tl = new InheritableThreadLocal();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            int temp = i;
            //模拟10个请求
            new Thread(new Runnable() {
                @Override
                public void run() {
                    tl.set("test" + temp);
                    System.out.println(Thread.currentThread().getName() + ":" + tl.get());
                    executorService.execute(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println(Thread.currentThread().getName() + ":" + tl.get());
                        }
                    });
                }
            }).start();

        }
    }

}

Thread-0:test0
Thread-5:test5
Thread-4:test4
Thread-2:test2
Thread-1:test1
Thread-8:test8
Thread-9:test9
Thread-3:test3
Thread-6:test6
Thread-7:test7
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6
pool-1-thread-1:test6

通过输出发现,10个请求,都各自设置了自己的TTL,但是最终池内线程,只输出一个tl,这就是数据污染,因为pool-1-thread-1线程是Thread-6创建的,因此copy test6,当池线程执行其他的任务时,拿到的都是test6。

说明:为了解决InheritableThreadLocal在线程池中的缺陷,衍生了:TransmittableThreadLocal

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值