Java 如何将MDC与ForkJoinPool一起使用?

Java 如何将MDC与ForkJoinPool一起使用?,java,slf4j,fork-join,mdc,Java,Slf4j,Fork Join,Mdc,跟进如何将MDC与ForkJoinPool一起使用?具体来说,我如何包装ForkJoinTask以便在执行任务之前设置MDC值?我不熟悉ForkJoinPool,但您可以将感兴趣的MDC键/值传递给ForkJoinTask实例,然后将它们提交到ForkJoinPool 鉴于从logback版本1.1.5开始,MDC值不是子线程继承的,因此没有太多选项。是的 在实例化实例时,将相关的MDC键/值传递给ForkJoinTask实例 扩展ForkJoinPool,以便将MDC键/值传递给新创建的线程

跟进如何将MDC与
ForkJoinPool
一起使用?具体来说,我如何包装
ForkJoinTask
以便在执行任务之前设置MDC值?

我不熟悉
ForkJoinPool
,但您可以将感兴趣的MDC键/值传递给
ForkJoinTask
实例,然后将它们提交到
ForkJoinPool

鉴于从logback版本1.1.5开始,MDC值不是子线程继承的,因此没有太多选项。是的

  • 在实例化实例时,将相关的MDC键/值传递给
    ForkJoinTask
    实例
  • 扩展
    ForkJoinPool
    ,以便将MDC键/值传递给新创建的线程
  • 创建自己的ThreadFactory,它将MDC键/值设置为新创建的线程

  • 请注意,我并没有实际实施任何一个选项2。或3

    以下内容似乎对我有用:

    import java.lang.Thread.UncaughtExceptionHandler;
    import java.util.Map;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.ForkJoinTask;
    import java.util.concurrent.atomic.AtomicReference;
    import org.slf4j.MDC;
    
    /**
     * A {@link ForkJoinPool} that inherits MDC contexts from the thread that queues a task.
     *
     * @author Gili Tzabari
     */
    public final class MdcForkJoinPool extends ForkJoinPool
    {
        /**
         * Creates a new MdcForkJoinPool.
         *
         * @param parallelism the parallelism level. For default value, use {@link java.lang.Runtime#availableProcessors}.
         * @param factory     the factory for creating new threads. For default value, use
         *                    {@link #defaultForkJoinWorkerThreadFactory}.
         * @param handler     the handler for internal worker threads that terminate due to unrecoverable errors encountered
         *                    while executing tasks. For default value, use {@code null}.
         * @param asyncMode   if true, establishes local first-in-first-out scheduling mode for forked tasks that are never
         *                    joined. This mode may be more appropriate than default locally stack-based mode in applications
         *                    in which worker threads only process event-style asynchronous tasks. For default value, use
         *                    {@code false}.
         * @throws IllegalArgumentException if parallelism less than or equal to zero, or greater than implementation limit
         * @throws NullPointerException     if the factory is null
         * @throws SecurityException        if a security manager exists and the caller is not permitted to modify threads
         *                                  because it does not hold
         *                                  {@link java.lang.RuntimePermission}{@code ("modifyThread")}
         */
        public MdcForkJoinPool(int parallelism, ForkJoinWorkerThreadFactory factory, UncaughtExceptionHandler handler,
            boolean asyncMode)
        {
            super(parallelism, factory, handler, asyncMode);
        }
    
        @Override
        public void execute(ForkJoinTask<?> task)
        {
            // See http://stackoverflow.com/a/19329668/14731
            super.execute(wrap(task, MDC.getCopyOfContextMap()));
        }
    
        @Override
        public void execute(Runnable task)
        {
            // See http://stackoverflow.com/a/19329668/14731
            super.execute(wrap(task, MDC.getCopyOfContextMap()));
        }
    
        private <T> ForkJoinTask<T> wrap(ForkJoinTask<T> task, Map<String, String> newContext)
        {
            return new ForkJoinTask<T>()
            {
                private static final long serialVersionUID = 1L;
                /**
                 * If non-null, overrides the value returned by the underlying task.
                 */
                private final AtomicReference<T> override = new AtomicReference<>();
    
                @Override
                public T getRawResult()
                {
                    T result = override.get();
                    if (result != null)
                        return result;
                    return task.getRawResult();
                }
    
                @Override
                protected void setRawResult(T value)
                {
                    override.set(value);
                }
    
                @Override
                protected boolean exec()
                {
                    // According to ForkJoinTask.fork() "it is a usage error to fork a task more than once unless it has completed
                    // and been reinitialized". We therefore assume that this method does not have to be thread-safe.
                    Map<String, String> oldContext = beforeExecution(newContext);
                    try
                    {
                        task.invoke();
                        return true;
                    }
                    finally
                    {
                        afterExecution(oldContext);
                    }
                }
            };
        }
    
        private Runnable wrap(Runnable task, Map<String, String> newContext)
        {
            return () ->
            {
                Map<String, String> oldContext = beforeExecution(newContext);
                try
                {
                    task.run();
                }
                finally
                {
                    afterExecution(oldContext);
                }
            };
        }
    
        /**
         * Invoked before running a task.
         *
         * @param newValue the new MDC context
         * @return the old MDC context
         */
        private Map<String, String> beforeExecution(Map<String, String> newValue)
        {
            Map<String, String> previous = MDC.getCopyOfContextMap();
            if (newValue == null)
                MDC.clear();
            else
                MDC.setContextMap(newValue);
            return previous;
        }
    
        /**
         * Invoked after running a task.
         *
         * @param oldValue the old MDC context
         */
        private void afterExecution(Map<String, String> oldValue)
        {
            if (oldValue == null)
                MDC.clear();
            else
                MDC.setContextMap(oldValue);
        }
    }
    
    import java.lang.Thread.UncaughtExceptionHandler;
    导入java.util.Map;
    导入java.util.concurrent.ForkJoinPool;
    导入java.util.concurrent.ForkJoinTask;
    导入java.util.concurrent.AtomicReference;
    导入org.slf4j.MDC;
    /**
    *一个{@link ForkJoinPool},它从对任务进行排队的线程继承MDC上下文。
    *
    *@作者Gili Tzabari
    */
    公共最终类MdcForkJoinPool扩展了ForkJoinPool
    {
    /**
    *创建新的MdcForkJoinPool。
    *
    *@param parallelism并行级别。对于默认值,请使用{@link java.lang.Runtime#availableProcessors}。
    *@param factory创建新线程的工厂。对于默认值,使用
    *{@link#defaultForkJoinWorkerThreadFactory}。
    *@param handler由于遇到不可恢复的错误而终止的内部工作线程的处理程序
    *在执行任务时。对于默认值,请使用{@code null}。
    *@param asyncMode如果为true,则为从未执行的分叉任务建立本地先进先出调度模式
    *在应用程序中,此模式可能比默认的基于本地堆栈的模式更合适
    *其中工作线程仅处理事件样式的异步任务。对于默认值,使用
    *{@code false}。
    *@如果并行度小于或等于零,或大于实现限制,则引发IllegalArgumentException
    *如果工厂为空,@将引发NullPointerException
    *如果存在安全管理器且不允许调用方修改线程,则@将引发SecurityException
    *因为它不成立
    *{@link java.lang.RuntimePermission}{@code(“modifyThread”)}
    */
    公共MdcForkJoinPool(int并行,ForkJoinWorkerThreadFactory工厂,UncaughtExceptionHandler,
    布尔异步模式)
    {
    超级(并行、工厂、处理程序、异步模式);
    }
    @凌驾
    public void execute(ForkJoinTask任务)
    {
    //看http://stackoverflow.com/a/19329668/14731
    execute(wrap(task,MDC.getCopyOfContextMap());
    }
    @凌驾
    公共void执行(可运行任务)
    {
    //看http://stackoverflow.com/a/19329668/14731
    execute(wrap(task,MDC.getCopyOfContextMap());
    }
    私有ForkJoinTask包装(ForkJoinTask任务,映射新上下文)
    {
    返回新的ForkJoinTask()
    {
    私有静态最终长serialVersionUID=1L;
    /**
    *如果非null,则重写基础任务返回的值。
    */
    private final AtomicReference override=新的AtomicReference();
    @凌驾
    公共T getRawResult()
    {
    T result=override.get();
    如果(结果!=null)
    返回结果;
    返回task.getRawResult();
    }
    @凌驾
    受保护的无效setRawResult(T值)
    {
    覆盖。设置(值);
    }
    @凌驾
    受保护的布尔执行()
    {
    //根据ForkJoinTask.fork()“除非任务已完成,否则多次fork任务是一种使用错误
    //因此,我们假设此方法不必是线程安全的。
    Map oldContext=执行前(newContext);
    尝试
    {
    task.invoke();
    返回true;
    }
    最后
    {
    后执行(oldContext);
    }
    }
    };
    }
    私有可运行包装(可运行任务,映射新上下文)
    {
    返回()->
    {
    Map oldContext=执行前(newContext);
    尝试
    {
    task.run();
    }
    最后
    {
    后执行(oldContext);
    }
    };
    }
    /**
    *在运行任务之前调用。
    *
    *@param newValue新的MDC上下文
    *@返回旧的MDC上下文
    */
    执行前私有映射(映射新值)
    {
    Map previous=MDC.getCopyOfContextMap();
    if(newValue==null)
    MDC.clear();
    其他的
    setContextMap(newValue);
    返回上一个;
    }
    /**
    *在运行任务后调用。
    *
    *@param oldValue旧MDC上下文
    */
    私有void afterExecution(映射oldValue)
    {
    if(oldValue==null)
    MDC.clear();
    其他的
    MDC.setContextMap(oldValue);
    }
    }
    

    import java.util.Map;
    导入java.util.concurrent.CountedCompleter;
    导入org.slf4j.MDC;
    /**
    *一个{@link CountedCompleter},它从执行以下操作的线程继承MDC上下文
    
    import java.util.Map;
    import java.util.concurrent.CountedCompleter;
    import org.slf4j.MDC;
    
    /**
     * A {@link CountedCompleter} that inherits MDC contexts from the thread that queues a task.
     *
     * @author Gili Tzabari
     * @param <T> The result type returned by this task's {@code get} method
     */
    public abstract class MdcCountedCompleter<T> extends CountedCompleter<T>
    {
        private static final long serialVersionUID = 1L;
        private final Map<String, String> newContext;
    
        /**
         * Creates a new MdcCountedCompleter instance using the MDC context of the current thread.
         */
        protected MdcCountedCompleter()
        {
            this(null);
        }
    
        /**
         * Creates a new MdcCountedCompleter instance using the MDC context of the current thread.
         *
         * @param completer this task's completer; {@code null} if none
         */
        protected MdcCountedCompleter(CountedCompleter<?> completer)
        {
            super(completer);
            this.newContext = MDC.getCopyOfContextMap();
        }
    
        /**
         * The main computation performed by this task.
         */
        protected abstract void computeWithContext();
    
        @Override
        public final void compute()
        {
            Map<String, String> oldContext = beforeExecution(newContext);
            try
            {
                computeWithContext();
            }
            finally
            {
                afterExecution(oldContext);
            }
        }
    
        /**
         * Invoked before running a task.
         *
         * @param newValue the new MDC context
         * @return the old MDC context
         */
        private Map<String, String> beforeExecution(Map<String, String> newValue)
        {
            Map<String, String> previous = MDC.getCopyOfContextMap();
            if (newValue == null)
                MDC.clear();
            else
                MDC.setContextMap(newValue);
            return previous;
        }
    
        /**
         * Invoked after running a task.
         *
         * @param oldValue the old MDC context
         */
        private void afterExecution(Map<String, String> oldValue)
        {
            if (oldValue == null)
                MDC.clear();
            else
                MDC.setContextMap(oldValue);
        }
    }
    
    import static org.hamcrest.Matchers.is;
    import static org.hamcrest.Matchers.startsWith;
    import static org.junit.Assert.assertThat;
    
    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.InputStreamReader;
    import java.nio.charset.Charset;
    import java.util.HashSet;
    import java.util.Map;
    import java.util.Set;
    import java.util.UUID;
    import java.util.concurrent.ForkJoinPool;
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.TimeUnit;
    import java.util.stream.IntStream;
    
    import org.junit.Test;
    import org.slf4j.LoggerFactory;
    import org.slf4j.MDC;
    
    import ch.qos.logback.classic.Level;
    import ch.qos.logback.classic.Logger;
    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.classic.spi.ILoggingEvent;
    import ch.qos.logback.core.OutputStreamAppender;
    
    public class MDCForkJoinPoolTest {
    
        private static final Logger log = (Logger) LoggerFactory.getLogger("mdc-test");
    
        // you can demonstrate the problem I'm trying to fix by changing the below to a normal ForkJoinPool and then running the test
        private ForkJoinPool threads = new MDCForkJoinPool(16);
        private Semaphore threadsRunning = new Semaphore(-99);
        private ByteArrayOutputStream bio = new ByteArrayOutputStream();
    
        @Test
        public void shouldCopyManagedDiagnosticContextWhenUsingForkJoinPool() throws Exception {
            for (int i = 0 ; i < 100; i++) {
                Thread t = new Thread(simulatedRequest(), "MDC-Test-"+i);
                t.setDaemon(true);
                t.start();
            }
    
            // set up the appender to grab the output
            LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
            OutputStreamAppender<ILoggingEvent> appender = new OutputStreamAppender<>();
            LogbackEncoder encoder = new LogbackEncoder();
            encoder.setPattern("%X{mdc_val:-}=%m%n");
            encoder.setContext(lc);
            encoder.start();
            appender.setEncoder(encoder);
            appender.setImmediateFlush(true);
            appender.setContext(lc);
            appender.setOutputStream(bio);
            appender.start();
            log.addAppender(appender);
            log.setAdditive(false);
            log.setLevel(Level.INFO);
    
            assertThat("timed out waiting for threads to complete.", threadsRunning.tryAcquire(300, TimeUnit.SECONDS), is(true));
    
            Set<String> ids = new HashSet<>();
            try (BufferedReader r = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(bio.toByteArray()), Charset.forName("utf8")))) {
                r.lines().forEach(line->{
                    System.out.println(line);
                   String[] vals = line.split("=");
                   if (!vals[0].isEmpty()) {
                       ids.add(vals[0]);
                       assertThat(vals[1], startsWith(vals[0]));
                   }
                });
            }
    
            assertThat(ids.size(), is(100));
        }
    
        private Runnable simulatedRequest() {
            return () -> {
                String id = UUID.randomUUID().toString();
                MDC.put("mdc_val", id);
                Map<String, String> context = MDC.getCopyOfContextMap();
                threads.submit(()->{
                    MDC.setContextMap(context);
                    IntStream.range(0, 100).parallel().forEach((i)->{
                       log.info("{} - {}", id, i); 
                    });
                }).join();
                threadsRunning.release();
            };
        }
    }
    
        @Override
        public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
            return super.submit(wrap(task, MDC.getCopyOfContextMap()));
        }
    
        @Override
        public <T> ForkJoinTask<T> submit(Callable<T> task) {
            return super.submit(wrap(task, MDC.getCopyOfContextMap()));
        }
    
        @Override
        public <T> ForkJoinTask<T> submit(Runnable task, T result) {
            return super.submit(wrap(task, MDC.getCopyOfContextMap()), result);
        }
    
        @Override
        public ForkJoinTask<?> submit(Runnable task) {
            return super.submit(wrap(task, MDC.getCopyOfContextMap()));
        }
    
        private <T> Callable<T> wrap(Callable<T> task, Map<String, String> newContext)
        {
            return () ->
            {
                Map<String, String> oldContext = beforeExecution(newContext);
                try
                {
                    return task.call();
                }
                finally
                {
                    afterExecution(oldContext);
                }
            };
        }