美文网首页
InheritableThreadLocal还存在的问题

InheritableThreadLocal还存在的问题

作者: 星空怎样 | 来源:发表于2020-05-28 17:50 被阅读0次

[toc]

问题场景

我们使用线程时候往往不会只是简单的new Thread对象,而是使用线程池,当然使用线程池的好处很多,那么线程池会给InheritableThreadLocal带来什么问题,我们列举一下线程池的特点:

  • 为了减少创建线程的开销,线程池会缓存已经使用过的线程
  • 生命周期统一管理,合理的分配系统资源(线程生命周期:新建状态、就绪状态、运行状态、阻塞状态、死亡状态)

对于第一点,如果子线程已经被使用过了,并且set了新的值到ThreadLocal中,那么第二个任务进来还能获得父线程的值吗?

final InheritableThreadLocal<Span> inheritableThreadLocal=new InheritableThreadLocal<>();
inheritableThreadLocal,set(new Span("xiexiexie"));
//输出xiexiexie
inheritableThreadLocal.get();
ExecutorService es=Executors.newFixedThreadPool(1);
es.execute(()->{
    System.out.println("========");
    //输出 xiexiexie
    inheritableThreadLocal.get();
    inheritableThreadLocal.set(new Span("qiqiqi");
    //输出 qiqiqi
    inheritableThreadLocal.get();
}});
TimeUnit.SECONDS.sleep(1);
es.execute(()->{
    System.out.println("========");
    //输出qiqiqi
    inheritableThreadLocal.get();
    inheritableThreadLocal.set(new Span("qiqiqi");
    //输出qiqiqi
    inheritableThreadLocal.get();
}});
TimeUnit.SECONDS.sleep(1);
System.out.prinln("========");
//输出xiexiexie
Span span = inheritableThreadLocal.get();

static class Span{
    public String name;
    public int age;
    public Span(String name){
        this.name=name;
    }
}
输出结果是:
xiexiexie
========
xiexiexie
qiqiqi
========
qiqiqi
qiqiqi
========
xiexiexie

造成这个结果的原因:只有创建子线程的时候才会设置子线程的inheritableTreadLocals值,假如第一次提交的任务是A,第二次是B,B任务提交任务时使用的是A的任务的缓存线程,A任务执行时已经重新set了InheritableThreadLocals,值已经变为qiqiqi,B任务再次获取时候直接从t.inheritableThreadLocals中获取,所以获得的是A任务提交的值,而不是父线程的值(父线程值没有改变的原因是子线程set的值,只会set到子线程对应的t.inheritableThreadLocals中,不会影响父线程的inheritableThreadLocals)

解决思路

在submit新任务的时候在重新copy父线程的所有的Entry,然后重新给t.inheritableThreadLocals赋值,这样就解决线程池中每一个新的任务都能获得父线程中的ThreadLocal的值,而不受其他任务影响,因为在生命周期完成时候会自动clear所有数据。

解决方案

第三方库

Alibaba的一个库解决了这个问题,github:alibaba/transmittable-thread-local(详细见GitHub文档)

自定义RunTask类

自定一个RunTask类,使用反射加代理的方式来实现业务,主线程存在InheritableThreaadLocal中的值间接复制,详细如下:

  • 定义一个InheritableTask抽象类,这个类实现了Runable接口,并定义一个runTask抽象方法,当开发者需要面对线程池,获取InheritableThreadLocal值的场景提交任务只需要集成InheritableTask类,实现runTask方法即可。
  • 在创建任务时,也就是InheritableTask构造方法中,通过反射获取提交任务的业务线程的inheritableLocals属性,然后复制一份,暂存到当前的task的inheritableThreadLocalsObj属性找那个
  • 线程池在执行该任务时,其实就是去掉用run()方法,在执行run方法时,先将inheritableThreadLocalsObj属性复制给当前执行任务的那个业务线程的inheritableThreadLocals属性值,然后再去执行runTask()方法,就是真正的业务逻辑,最后finally清理掉执行当前业务的线程的inheritableThreadLocals属性。

详细代码如下:

public abstract class InheritableTask implements Runnable {
    private Object inheritableThreadLocalsObj;

    public InheritableTask() {
        try {
            //获取当前业务线程
            Thread currentThread = Thread.currentThread();
            //获取inheritableThreadLocals属性值
            Field inheritableThreadLocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
            inheritableThreadLocalsField.setAccessible(true);
            //得到当前线程inheritableThreadLocals的属性值
            Object threadLocalMapObj = inheritableThreadLocalsField.get(currentThread);
            if (null != threadLocalMapObj) {
                //获取字段的类型
                Class<?> threadLocalMapClazz = inheritableThreadLocalsField.getType();
                //获取ThreadLocal中的createInheritedMap方法
                Method method = ThreadLocal.class.getDeclaredMethod("createInheritedMap", threadLocalMapClazz);
                method.setAccessible(true);
                //调用createInheritedMap方法,重新创建一个新的inheritableThreadLocals,并且将这个值保存
                this.inheritableThreadLocalsObj = method.invoke(ThreadLocal.class, threadLocalMapObj);
            }

        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    @Override
    public void run() {
        //此处获取处理当前业务的线程,也就是线程池中的线程
        Thread currentThread = Thread.currentThread();
        Field field = null;
        try {
            //获取inheritableThreadLocals属性
            field = Thread.class.getDeclaredField("inheritableThreadLocals");
            //设置权限
            field.setAccessible(true);
            if (this.inheritableThreadLocalsObj != null) {
                //将暂存值,赋值给currentThread
                field.set(currentThread, this.inheritableThreadLocalsObj);
                inheritableThreadLocalsObj = null;
            }
            //执行任务
            runTask();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            try {
                //最后将线程的InheritableThreadLocals设置为null
                if (field != null) {
                    field.set(currentThread, null);
                }
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            }
        }
    }

    /**
     * 代理方法这个方法处理业务逻辑
     */
    public abstract void runTask();

下面是测试用例:

public class ThreadLocalTest {
    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
        inheritableThreadLocal.set("qis");
        System.out.println(inheritableThreadLocal.get());
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.execute(() -> {
            System.out.println(inheritableThreadLocal.get());
            inheritableThreadLocal.set("qishuo");
            System.out.println(inheritableThreadLocal.get());
        });
        Thread.sleep(1000);
        System.out.println("----------------");
        executorService.execute(() -> {
            System.out.println(inheritableThreadLocal.get());
        });
        Thread.sleep(1000);
        System.out.println("--------------分隔新以上是没有使用InheritableTask----------------");

        executorService.submit(new InheritableTask() {
            @Override
            public void runTask() {
                System.out.println(inheritableThreadLocal.get());
                inheritableThreadLocal.set("qishuo");
                System.out.println(inheritableThreadLocal.get());
            }
        });
        Thread.sleep(1000);
        System.out.println("----------------");
        executorService.submit(new InheritableTask() {
            @Override
            public void runTask() {
                System.out.println(inheritableThreadLocal.get());
            }
        });
    }
}

输出结果:

qis
qis
qishuo
----------------
qishuo
--------------分隔新以上是没有使用InheritableTask----------------
qis
qishuo
----------------
qis

这样就解决了在线程池场景下的InheritableThreadLocal无效的问题,然而反射比较耗性能,一般优化反射的两种方式,一种使用缓存,一种使用性能较高的反射工具比如RefelectASM类。

下面展示使用缓存的实现:

public abstract class InheritableTaskWithCache implements Runnable {
    private Object threadLocalsMapObj;
    private static volatile Field inheritableThreadLocalsField;
    private static volatile Class threadLocalMapClazz;
    private static volatile Method createInheritedMapMethod;
    private static final Object accessLock = new Object();

    public InheritableTaskWithCache() {
        try {
            Thread currentThread = Thread.currentThread();
            Field field = getInheritableThreadLocalsField();
            //得到当前线程的inheritableThreadLocals的值ThreadLocalMap
            Object threadLocalsMapObj = field.get(currentThread);
            if (null != threadLocalsMapObj) {
                Class threadLocalMapClazz = getThreadLocalMapClazz();
                Method method = getCreateInheritedMapMethod(threadLocalMapClazz);
                //创建一个新的ThreadLocalMap
                this.threadLocalsMapObj = method.invoke(ThreadLocal.class, threadLocalsMapObj);
            }
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }

    private Field getInheritableThreadLocalsField() {
        if (null == inheritableThreadLocalsField) {
            synchronized (accessLock) {
                if (null == inheritableThreadLocalsField) {
                    try {
                        Field field = Thread.class.getDeclaredField("inheritableThreadLocals");
                        field.setAccessible(true);
                        inheritableThreadLocalsField = field;
                    } catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                }
            }
        }
        return inheritableThreadLocalsField;
    }

    private Method getCreateInheritedMapMethod(Class threadLocalMapClazz) {
        if (null != threadLocalMapClazz && null == createInheritedMapMethod) {
            synchronized (accessLock) {
                if (null == createInheritedMapMethod) {
                    try {
                        Method method = ThreadLocal.class.getDeclaredMethod("createInheritedMap", threadLocalMapClazz);
                        method.setAccessible(true);
                        createInheritedMapMethod = method;
                    } catch (Exception e) {
                        throw new IllegalStateException(e);
                    }
                }
            }
        }
        return createInheritedMapMethod;
    }

    private Class getThreadLocalMapClazz() {
        if (null == inheritableThreadLocalsField) {
            return null;
        }
        if (null == threadLocalMapClazz) {
            synchronized (accessLock) {
                if (null == threadLocalMapClazz) {
                    threadLocalMapClazz = inheritableThreadLocalsField.getType();
                }
            }
        }
        return threadLocalMapClazz;
    }

    /**
     * 代理方法这个方法处理业务逻辑
     */
    protected abstract void runTask();

    @Override
    public void run() {
        Thread currentThread = Thread.currentThread();
        Field field = getInheritableThreadLocalsField();
        try {
            if (null != threadLocalsMapObj && null != field) {
                field.set(currentThread, threadLocalsMapObj);
                threadLocalsMapObj = null;
            }
            runTask();
        } catch (Exception e) {
            throw new IllegalStateException(e);
        } finally {
            try {
                if (field != null) {
                    field.set(currentThread, null);
                }
            } catch (Exception e) {
                System.out.println(e.toString());
            }
        }
    }
}

综上,通过一个抽象的InheritableTask解决了线程池场景下InheritableThreadLocal失效问题。

总结

  • InheritableThreadLocal在线程池中无效的原因是只有在创建线程Thread时才会去赋值父线程的InheritableThreadLocal中的值,而线程池场景下,主业务线程仅仅是提交任务的队列中的
  • 如果要解决这个问题,可以自定义一个RunTask类,通过反射加代理的方式来实现业务主线程存在InheritableThreadLocal中值的间接复制,或者使用阿里开源的transmittable-thread-local

相关文章

网友评论

      本文标题:InheritableThreadLocal还存在的问题

      本文链接:https://www.haomeiwen.com/subject/tawaahtx.html