源于工作中一个业务场景的需求。
话不多说,先贴完整的源码:
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());
}
}
改动点:对提交的Runnable以及Callable进行包装,下面就看它们是如何封装的。
重点关注上面那一段注释,构造方法是调用线程执行的,所以使用ThreadLocal去存储的话,最终是写入到调用线程上下文中的。
run方法的执行代表新的线程已经产生,不清楚的可以看之前的博客,然后新的线程又持有Runnable对象的引用,所以可以取到存储的contextValue,放入到当前线程的上下文,实现上下文在线程之间传递。
Callable与之类似不再赘述。