搜索
写经验 领红包
 > 健康

附近人聊一聊(关于古诗的奇妙简单跟你聊一聊)

导语:聊一聊ThreadLocal

附近人聊一聊(关于古诗的奇妙简单跟你聊一聊)

在编码过程中,为了解决多线程并发问题,常常会采用ThreadLocal(线程局部变量),然而有时需把线程局部变量传递给新开的子线程,此时我们需要用到InheritableThreadLocal 。

线程局部变量-ThreadLocal

ThreadLocal常用来每个线程需要有自己单独的实例并且该实例需要在多个方法间共享,线程之间不需要共享。例如,数据库连接等。

// 1、定义一个常量ThreadLocalprivate static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<String>();public static void main(String[] args) {    ExecutorService executorService = Executors.newFixedThreadPool(2);    for(int i=0;i<10;i++) {        final int index = i;        executorService.submit(() -> {            // 2、线程中使用时,set数据            THREAD_LOCAL.set(&34;+index);            System.out.println(Thread.currentThread().getName()+&34;+index);            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                throw new RuntimeException(e);            }            // 3、可以取出数据            System.out.println(Thread.currentThread().getName()+&34;+index+&34;+THREAD_LOCAL.get());        });    }}

运行结果:

可以看到线程交替运行,并不会导致线程间数据共享。那么是怎么实现的呢?可以按照下图理解:

ThreadLocal下面有个Map结构为ThreadLocalMap,在调用

THREAD_LOCAL.set(&34;+index);

时会有如下逻辑

 public void set(T value) {  // 1、获取当前线程,为native方法    Thread t = Thread.currentThread();    // 2、根据当前线程获取ThreadLocalMap    ThreadLocalMap map = getMap(t);    if (map != null)      // map 的key为当前set的ThreadLocal实例,value就是线程绑定变量        map.set(this, value);    else        createMap(t, value);}

到此,可以看到就是通过ThreadLocalMap结构维护了线程绑定数据的关系,而ThreadLocal对外提供了一个操作的入口,多个ThreadLocal不会互相影像。

那么,线程池中的线程Set过一次,如果没有重新Set还会拿到原值吗?

 // 判断是否有数据,没有set数据if(THREAD_LOCAL.get()==null) {    THREAD_LOCAL.set(&34; + index);}

输出结果:

从结果上可以看到,不会在改变。这也是为什么在一些开发手册上强调ThreadLocal容易导致内存泄露,需要用完及时清理。

那么如果在开子线程,怎么把ThreadLocal中的信息传给子线程呢?

可遗传的线程局部变量-InheritableThreadLocal

见名知意,那么这个怎么用呢,如下:

 // 1、定义一个常量ThreadLocal    private static final InheritableThreadLocal<String> I_THREAD_LOCAL = new InheritableThreadLocal<String>();    public static void main(String[] args) {        ExecutorService executorService = Executors.newFixedThreadPool(2);        for(int i=0;i<10;i++) {            final int index = i;            executorService.submit(() -> {                // 2、线程中使用时,set数据                I_THREAD_LOCAL.set(&34; + index);                System.out.println(Thread.currentThread().getName()+&34;+index);                childThread(index);                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    throw new RuntimeException(e);                }                // 3、可以取出数据                System.out.println(Thread.currentThread().getName()+&34;+index+&34;+I_THREAD_LOCAL.get());            });        }    }    // 4、子线程输出    public static void childThread(int index){        new Thread(()->{            System.out.println(Thread.currentThread().getName()+&34;+index+&34;+I_THREAD_LOCAL.get());            I_THREAD_LOCAL.set(&34; + index);            System.out.println(Thread.currentThread().getName()+&34;+index+&34;+I_THREAD_LOCAL.get());        }).start();    }

输出结果:

可以看到InheritableThreadLocal可以把在子线程中拿到数据,并且子线程重新set的值,不会更改到父线程。InheritableThreadLocal继承自ThreadLocal重写了几个方法

public class InheritableThreadLocal<T> extends ThreadLocal<T> {    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);    }}

那么为什么子线程set修改的数据不能在父线程中那到呢?其实,在new Thread()会调用

在init方法中有

parent为线程池中的线程,那么parent.inheritableThreadLocals也就不为空,自然下面就把父线程数据给赋值给了子线程。

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

此时创建了一个新的ThreadLocalMap,自然就无法更改父类。

那么为什么子线程是线程池呢?子线程用了线程池,可以看到子线程的inheritableThreadLocals在在第一次实例化后创建完成,后面没有对inheritableThreadLocals在做更改,所以使用线程池产生子线程只会有第一次使用时父类的绑定变量。那么怎么解决这个问题呢?可以使用TransmittableThreadLocal。他是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩TransmittableThreadLocal,配合 TtlRunnable 和 TtlCallable进行使用解决。

今天您学会了吗[兔年大吉]

本文内容由小鸣整理编辑!