线程之间如何传递上下文信息

发布时间:2024年01月15日

源于工作中一个业务场景的需求。

源码

话不多说,先贴完整的源码:

public class ContextPassingBetweenThread {
    private static ThreadLocal<String> CONTEXT = new ThreadLocal<>();
    private static ExecutorService executor = new ThreadPoolExecutor(1, 1,
            60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512));

    private static ExecutorService executorWrap = new ThreadPoolExecutorWrap(1, 1,
            60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(512));
	/**
  	 * 编码时可以把下面三个静态内部类拎出去,放这里方便解释
  	 */
    static class ThreadPoolExecutorWrap extends ThreadPoolExecutor {
        public ThreadPoolExecutorWrap(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
            super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        }

        @Override
        public Future<?> submit(Runnable task) {
            if (task == null) {
                throw new NullPointerException();
            }
            RunnableFuture<Void> ftask = newTaskFor(new RunnableWrap(task), null);
            execute(ftask);
            return ftask;
        }
		
		@Override
        public <T> Future<T> submit(Callable<T> task) {
            if (task == null) throw new NullPointerException();
            RunnableFuture<T> ftask = newTaskFor(new CallableWrap(task));
            execute(ftask);
            return ftask;
        }


    }

    static class RunnableWrap implements Runnable {
        private String contextValue;
        private Runnable task;

        public RunnableWrap(Runnable task) {
          	// 注意此处用属性先保存上下文的内容,应为到另一个线程里面调用get方法,
          	// 那么会是其他线程上下文,所以需要一个东西暂时存储
            this.contextValue = CONTEXT.get();
            this.task = task;
        }

        @Override
        public void run() {
            try {
                CONTEXT.set(contextValue);
                // 用户任务逻辑
                task.run();
            } finally {
                CONTEXT.remove();
            }
        }
    }

    static class CallableWrap<V> implements Callable<V>{
        private String contextValue;
        private Callable<V> task;

        public CallableWrap(Callable<V> task) {
            this.contextValue = CONTEXT.get();
            this.task = task;
        }

        @Override
        public V call() throws Exception {
            V call = null;
            try {
                CONTEXT.set(contextValue);
                call = task.call();
            } finally {
                CONTEXT.remove();
            }
            return call;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CONTEXT.set("main context");

        // 方式1:在用户任务中直接进行手动获取/设置上下文逻辑
        executor.submit(new RunnableWrap(() -> System.out.println("hello world: " + CONTEXT.get())));

        // 方式2:自定义线程池,封装成支持保存/设置上下文的任务
        // 无返回值
        executorWrap.submit(() -> System.out.println("hello world: " + CONTEXT.get()));
        // 有返回值
        Future<String> submit = executorWrap.submit(() -> "hello" + CONTEXT.get());
        System.out.println(submit.get());
    }
}

解读

1. 扩展ThreadPoolExecutor

code
改动点:对提交的Runnable以及Callable进行包装,下面就看它们是如何封装的。

2. 扩展Runnable

code
重点关注上面那一段注释,构造方法是调用线程执行的,所以使用ThreadLocal去存储的话,最终是写入到调用线程上下文中的。
run方法的执行代表新的线程已经产生,不清楚的可以看之前的博客,然后新的线程又持有Runnable对象的引用,所以可以取到存储的contextValue,放入到当前线程的上下文,实现上下文在线程之间传递。

Callable与之类似不再赘述。

3. 整体流程

流程

文章来源:https://blog.csdn.net/flyingmylife/article/details/135575910
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。