线程池和InheritedThreadLocal

3
我看到了下面的问题。答案是使用信号量,但这并没有回答我在问题中遇到的另一个问题。 使用InheritableThreadLocal与ThreadPoolExecutor -- 或 -- 不重用线程的ThreadPoolExecutor 我有一个父线程,为每个新请求设置一些唯一标识符,并提交2个可运行任务到线程池,即2个线程。对于初始请求,在父线程中设置的InheritedThreadLocal的值被正确地传播到子线程。对于下一个请求,子线程没有接收到父线程设置的最新InheritedThreadLocal,并且在ChildThread中使用了旧值。这是因为线程池重用线程,而InheritedThreadLocal只在创建新线程时复制。现在我该如何在线程池场景中将最新的InheritedThreadLocal值从父线程传播到子线程?有什么解决方法吗?

我尝试过谷歌搜索,但无法找到确切的答案来解决这个问题。 - crackerplace
我也遇到了同样的问题。你最终找到解决方案了吗?如果有,请将其添加为答案。 - rahulmohan
@rahulmohan 不知何故,我们没有继续进行上述要求,因此我们没有进一步尝试。如果有帮助的话,请查看这个链接:https://dev59.com/_Gw05IYBdhLWcg3wVwc4#7297494。 - crackerplace
只有在创建子线程时,父线程的线程本地值才会传递给子线程。一旦该子线程在初始执行后返回到池中或从那时起多次执行,它就无法访问父线程上下文。 - crackerplace
https://www.securecoding.cert.org/confluence/display/java/TPS04-J.+Ensure+ThreadLocal+variables+are+reinitialized+when+using+thread+pools - crackerplace
2个回答

2

我写这些方法是因为我需要它们。

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ThreadPoolExecutor;

public class EnvUtils {
    /**
     * Extract the current inheritableThreadLocals map from the current thread.
     * Typical usage is in a threadpool, where:
     * <ul>
     * <li>You run {@link EnvUtils#extract()} in the running thread, to store
     * the information somewhere
     * <li>You create a method {@link ThreadPoolExecutor#beforeExecute()} in which
     * you run {@link EnvUtils#copy(Object)} with the above-stored information.
     * </ul>
     *
     * @return The current inheritableThreadLocals of the current thread
     */
    public static Object extract() {
        Object toreturn = null;
        try {
            // get field descriptor
            Field inthlocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
            inthlocalsField.setAccessible(true);
            //the object stored there
            Object inthlocalsMap = inthlocalsField.get(Thread.currentThread());
            // no need to copy, it will be done by the copy() method below
            toreturn = inthlocalsMap;
        } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
            // This may happen in a different Java implementation
            throw new RuntimeException(e);
        }
        return toreturn;
    }

    /**
     * Replaces the field inheritableThreadLocals of the current thread with the values provided.<br/>
     * It is the same as if the current thread was just created from the thread whose <code>stored</code>
     * values come from.<br/>
     * Must be called in the thread which want to inherit from given {@link inheritableThreadLocals} map.<br/>
     * <b>Note 1:</b> This does not modify non-inheritable thread locals<br/>
     * <b>Note 2:</b> This delete all previous values of {@link inheritableThreadLocals} in the current thread<br/>
     *
     * @param stored
     *            The stored inheritableThreadLocals value, coming from the extract() method
     */
    public static void copy(final Object stored) {
        try {
            // find ThreadLocalMap class
            String threadLocalClassName = ThreadLocal.class.getName();
            Class<?> threadLocaLMapClass = Class.forName(threadLocalClassName + "$ThreadLocalMap");
            // check that given object is an instance of the class
            if (stored == null || !threadLocaLMapClass.isInstance(stored)) {
                throw new IllegalArgumentException("Given object is not a ThreadLocalMap: " + stored);
            }
            // get constructor of ThreadLocalMap
            Constructor<?> creator = threadLocaLMapClass.getDeclaredConstructor(threadLocaLMapClass);
            creator.setAccessible(true);
            // get field descriptor of the thread
            Field inthlocalsField = Thread.class.getDeclaredField("inheritableThreadLocals");
            inthlocalsField.setAccessible(true);
            // create new inherited map
            Object newObj = creator.newInstance(stored);
            // set it to the current thread
            inthlocalsField.set(Thread.currentThread(), newObj);

        } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | NoSuchFieldException
                | IllegalAccessException | IllegalArgumentException | InvocationTargetException
                | InstantiationException e) {
            // This may happen in a different Java implementation
            throw new RuntimeException(e);
        }

    }

}

EnvUtils.extract()返回对当前线程的inheritableThreadLocals映射的引用。

现在,当您创建一个要在ThreadPool中调用的Runnable时,只需将inheritableThreadInfo = EnvUtils.extract()存储在字段中,并在其run()方法中调用EnvUtils.copy(inheritableThreadInfo)

注意:此解决方案使用了大量反射,因此受Java实现的影响。我在Oracle Java 1.8上进行了测试。


0
如果我们像这样配置ThreadPoolExecutor
new ThreadPoolExecutor(0, 2, 0L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());

每次都会创建一个新的线程,因此 InheritableThreadLocals 将被继承,但这时它几乎不能被称为 ThreadPoolExecutor。


1
在这种情况下,是的。那将不是线程池。关键是我们不使用ThreadPoolExecutor来管理线程。我们使用IBM实现的javax.resource.spi.work.WorkManager来管理线程。 - crackerplace
另外需要补充的一点是,问题可以简化为:是否有任何方法可以在线程池中的线程执行给定的可运行对象时,将父线程上下文中的InheritedThreadLocals传递到子线程上下文中? - crackerplace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接