线程之间数据传递详解

前言

在业务系统的开发过程中,我们经常会遇到父子线程数据传递(非调用参数)的场景,如:登陆信息,调用者信息,TraceId的传递等业务场景,固总结4中方式进行线程之间数据传递。

ThreadLocal

  • 代码如下:
public class TtlParameterWrapper {

    private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
 
    private TtlParameterWrapper() {
    }
 
    public static String getCaller() {
        return THREAD_LOCAL.get();
    }
 
    public static void setCaller(String caller) {
       THREAD_LOCAL.set(caller);
    }
 
    public static void clear() {
       THREAD_LOCAL.remove();
    }
}

那么子线程想要获取这个TtlParameterWrapper如何做呢?

    1. 获取父线程的TtlParameterWrapper
    1. 将TtlParameterWrapper设置到子线程,达到复用
public void handler(){
        // 1. 获取父线程
       TtlParameterWrapper.setCaller("caller path");
       log.info("父线程的值 ->{}",TtlParameterWrapper.get());
       CompletableFuture.runAsync(()->{
            // 2. 设置子线程的值,复用
          TtlParameterWrapper.setCaller("caller path");
          log.info("子线程的值 ->{}", TtlParameterWrapper.getCaller());
        });
    }

总结

虽然最终达成了传递的目的,但是每次开异步线程都需要手动设置,代码冗余繁杂,如果不这样设置则无法跨线程进行传递;如果手动设置,将无法进行线程间进行传递,因为TheadLocal中的数据无法进行线程间进行传递。

InheritableThreadLocal

这种方案不建议使用,InheritableThreadLocal虽然能够实现父子线程间的复用,但是在线程池中使用会存在失败的问题,原因:InheritableThreadLocal 在父线程创建子线程的时候,会将父线程中InheritableThreadLocal中存储的数据 拷贝一份存储到子线程的 InheritableThreadLocal中,但是在web的容器中使用了线程池,线程会被创建回收重复的利用,不会被销毁重新创建,所以会存在实效的场景。
这种方案使用也是非常简单,直接用InheritableThreadLocal替换ThreadLocal即可。

  • 代码如下:
public class TtlParameterWrapper {

    private static  final  InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
 
    public static String getCaller(){
        return inheritableThreadLocal.get();
    }
    public static void setCaller(LoginVal loginVal){
       inheritableThreadLocal.set(loginVal);
    }
    public static void clear(){
       inheritableThreadLocal.remove();
    }
}

TransmittableThreadLocal

TransmittableThreadLocal是阿里开源的工具,解决了InheritableThreadLocal不能进行线程池间传递数据的缺陷,在使用线程池等会池化复用线程的执行组件情况下,提供ThreadLocal值的传递功能,解决异步执行时上下文传递的问题。

  • 添加依赖
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>transmittable-thread-local</artifactId>
	<version>2.14.2</version>
</dependency>
  • TtlParameterWrapper改造
public class TtlParameterWrapper {
 
    private static final TransmittableThreadLocal<String> TRANSMITTABLE_THREAD_LOCAL = new TransmittableThreadLocal<>();
 
    private TtlParameterWrapper() {
    }
 
    public static String getCaller() {
        return TRANSMITTABLE_THREAD_LOCAL.get();
    }
 
    public static void setCaller(String caller) {
       TRANSMITTABLE_THREAD_LOCAL.set(caller);
    }
 
    public static void clear() {
        TRANSMITTABLE_THREAD_LOCAL.remove();
    }
}

原理

从定义来看,TransimittableThreadLocal继承于InheritableThreadLocal,并实现TtlCopier接口,它里面只有一个copy方法。所以主要是对InheritableThreadLocal的扩展。

public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> implements TtlCopier<T>

在TransimittableThreadLocal中添加holder属性。这个属性的作用就是被标记为具备线程传递资格的对象都会被添加到这个对象中。
要标记一个类,比较容易想到的方式,就是给这个类新增一个Type字段,还有一个方法就是将具备这种类型的的对象都添加到一个静态全局集合中。之后使用时,这个集合里的所有值都具备这个标记。

//1. holder本身是一个InheritableThreadLocal对象
//2. 这个holder对象的value是WeakHashMap<TransmittableThreadLocal<Object>,?>
//   2.1WeekHashMap的value总是null,且不可能被使用。
//    2.2WeekHasshMap支持value=null
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>> holder = new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<Object>,?>>(){
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> initialValue() {
	return new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
}

/**
 * 重写了childValue方法,实现上直接将父线程的属性作为子线程的本地变量对象。   
 */
@Override
protected WeakHashMap<TransmittableThreadLocal<Object>, ?> childValue(WeakHashMap<TransmittableThreadLocal<Object>, ?>parentValue){
	return new WeakHashMap<TransmittableThreadLocal<Object>, Object>(parentValue);
}};

应用代码是通过TtlExecutors工具类对线程池对象进行包装。工具类只是简单的判断,输入的线程池是否已经被包装过、非空校验等,然后返回包装类ExecutorServiceTtlWrapper。根据不同的线程池类型,有不同和的包装类。

@Nullable
public static ExecutorServicegetTtlExecutorService(@Nullable ExecutorService executorService){
	if (TtlAgent.isTtlAgentLoaded() || executorService == null || executorService instanceof TtlEnhanced){
		return executorService;
	}
	return new ExecutorServiceTtlWrapper(executorService);
}
进入包装类ExecutorServiceTtlWrapper。可以注意到不论是通过ExecutorServiceTtlWrapper#submit方法或者是ExecutorTtlWrapper#execute方法,都会将线程对象包装成TtlCallable或者TtlRunnable,用于在真正执行run方法前做一些业务逻辑。
/** 
 * 在ExecutorServiceTtlWrapper实现submit方法
 */
@NonNull
@Override
public <T> Future<T> submit(@NonNull Callable<T> task){
	return executorService.submit(TtlCallable.get(task));
}
 
/** 
 * 在ExecutorTtlWrapper实现execute方法
 */
@Override
public void execute(@NonNull Runnable command){
	executor.execute(TtlRunnable.get(command));
}

重点的核心逻辑应该是在TtlCallable#call()或者TtlRunnable#run()中。以下以TtlCallable为例,TtlRunnable同理类似。在分析call()方法之前,先看一个类Transmitter。

public static class Transmitter {
	/**    
	 * 捕获当前线程中的是所有TransimittableThreadLocal和注册ThreadLocal的值。    
	 */
	@NonNull
	public static Object capture(){
		return new Snapshot(captureTtlValues(), captureThreadLocalValues());
	}
	 
	/**    
	 * 捕获TransimittableThreadLocal的值,将holder中的所有值都添加到HashMap后返回。    
	 */
	private static HashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
		HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
		for(TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
			ttl2Value.put(threadLocal, threadLocal.copyValue());
		}
		return ttl2Value;
	}
 
	/**    
	 * 捕获注册的ThreadLocal的值,也就是原本线程中的ThreadLocal,可以注册到TTL中,在    
	 * 进行线程池本地变量传递时也会被传递。   
	 */
	private static HashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
		final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
		for(Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
			final ThreadLocal<Object> threadLocal = entry.getKey();
			final TtlCopier<Object> copier = entry.getValue();
			threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
		}
		return threadLocal2Value;
	}
 
	/**    
	 * 将捕获到的本地变量进行替换子线程的本地变量,并且返回子线程现有的本地变量副本backup。   
	 * 用于在执行run/call方法之后,将本地变量副本恢复。    
	 */
	@NonNull
	public static Object replay(@NonNull Object captured) {
		final Snapshot capturedSnapshot = (Snapshot) captured;
		return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
	}
 
	/**    
	 * 替换TransmittableThreadLocal    
	 */
	@NonNull
	private static HashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> captured) {
		// 创建副本backup
		HashMap<TransmittableThreadLocal<Object>, Object> backup = new HashMap<TransmittableThreadLocal<Object>, Object>();
	
		for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();) {
			TransmittableThreadLocal<Object> threadLocal = iterator.next();
			// 对当前线程的本地变量进行副本拷贝
			backup.put(threadLocal, threadLocal.get());
	
			// 若出现调用线程中不存在某个线程变量,而线程池中线程有,则删除线程池中对应的本地变量
			if (!captured.containsKey(threadLocal)){
				iterator.remove();
				threadLocal.superRemove();
			}
		}
		// 将捕获的TTL值打入线程池获取到的线程TTL中。
		setTtlValuesTo(captured);
		// 是一个扩展点,调用TTL的beforeExecute方法。默认实现为空
		doExecuteCallback(true);
		return backup;
	}
 
	private static HashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>, Object> captured) {
		final HashMap<ThreadLocal<Object>, Object> backup = new HashMap<ThreadLocal<Object>, Object>();
		for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
			final ThreadLocal<Object> threadLocal = entry.getKey();
			backup.put(threadLocal, threadLocal.get());
			final Objectvalue = entry.getValue();
			if (value == threadLocalClearMark) threadLocal.remove();
			else threadLocal.set(value);
		}
		return backup;
	}
 
	/**    
	 * 清除单线线程的所有TTL和TL,并返回清除之气的backup    
	 */
	@NonNull
	public static Object clear() {
		final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new HashMap<TransmittableThreadLocal<Object>, Object>();
		final HashMap<ThreadLocal<Object>, Object> threadLocal2Value = new HashMap<ThreadLocal<Object>, Object>();
		for (Map.Entry<ThreadLocal<Object>,TtlCopier<Object>>entry : threadLocalHolder.entrySet()) {
			final ThreadLocal<Object> threadLocal = entry.getKey();
			threadLocal2Value.put(threadLocal, threadLocalClearMark);
		}
		return replay(new Snapshot(ttl2Value, threadLocal2Value));
	}
 
	/**    
	 * 还原    
	 */
	public static void restore(@NonNull Object backup) {
		final Snapshot backupSnapshot = (Snapshot) backup;
		restoreTtlValues(backupSnapshot.ttl2Value);
		restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
	}
 
	private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> backup) {
		// 扩展点,调用TTL的afterExecute
		doExecuteCallback(false);
	
		for (finalIterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext();) {
			TransmittableThreadLocal<Object> threadLocal = iterator.next();
			
			if (!backup.containsKey(threadLocal)) {
				iterator.remove();
				threadLocal.superRemove();
			}
		}
	
		// 将本地变量恢复成备份版本
		setTtlValuesTo(backup);
	}
 
	private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal<Object>,Object> ttlValues) {
		for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry :ttlValues.entrySet()) {
			TransmittableThreadLocal<Object> threadLocal = entry.getKey();
			threadLocal.set(entry.getValue());
		}
	}
 
	private staticvoid restoreThreadLocalValues(@NonNull HashMap<ThreadLocal<Object>,Object> backup) {
		for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
			final ThreadLocal<Object> threadLocal = entry.getKey();
			threadLocal.set(entry.getValue());
		}
	}
 
	/**   
	 * 快照类,保存TTL和TL   
	 */
	private static class Snapshot {
		final HashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
		final HashMap<ThreadLocal<Object>, Object> threadLocal2Value;
		
		private Snapshot(HashMap<TransmittableThreadLocal<Object>, Object>ttl2Value, HashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
			this.ttl2Value= ttl2Value;
			this.threadLocal2Value= threadLocal2Value;
		}
	}
	// 进入TtlCallable#call()方法。
	@Override
	public V call() throws Exception {
		Object captured = capturedRef.get();
		if (captured == null|| releaseTtlValueReferenceAfterCall && !capturedRef.compareAndSet(captured, null)) {
			throw new IllegalStateException("TTLvalue reference is released after call!");
		}
		// 调用replay方法将捕获到的当前线程的本地变量,传递给线程池线程的本地变量,
		// 并且获取到线程池线程覆盖之前的本地变量副本。
		Object backup = replay(captured);
		try {
			// 线程方法调用
			return callable.call();
		} finally {
			// 使用副本进行恢复。
			restore(backup);
		}
	}
}

线程池方式传递本地变量的核心代码已经完毕。总的来说在创建TtlCallable对象是,调用capture()方法捕获调用方的本地线程变量,在call()执行时,将捕获到的线程变量,替换到线程池所对应获取到的线程的本地变量中,并且在执行完成之后,将其本地变量恢复到调用之前。

TaskDecorator

线程池设置TaskDecorator,TaskDecorator是什么?

官方释义:这是一个执行回调方法的装饰器,主要应用于传递上下文,或者提供任务的监控/统计信息。
  • 代码如下
public class ContextTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable runnable){
        //获取父线程的值
        String callerPath = TtlParameterWrapper.getCaller();
        return () -> {
            try {
                // 将主线程的请求信息,设置到子线程中
               TtlParameterWrapper.setCaller(callerPath);
                // 执行子线程,这一步不要忘了
               runnable.run();
            } finally {
                // 线程结束,清空这些信息,否则可能造成内存泄漏
               TtlParameterWrapper.clear();
            }
        };
    }
}

TaskDecorator需要结合线程池使用,实际开发中异步线程建议使用线程池,只需要在对应的线程池配置一下

  • 代码
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
       ThreadPoolTaskExecutor poolTaskExecutor = new ThreadPoolTaskExecutor();
       poolTaskExecutor.setCorePoolSize(xx);
       poolTaskExecutor.setMaxPoolSize(xx);
        // 设置线程活跃时间(秒)
       poolTaskExecutor.setKeepAliveSeconds(xx);
        // 设置队列容量
       poolTaskExecutor.setQueueCapacity(xx);
        //设置TaskDecorator,用于解决父子线程间的数据复用
       poolTaskExecutor.setTaskDecorator(new ContextTaskDecorator());
       poolTaskExecutor.setRejectedExecutionHandler(newThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
       poolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
       return poolTaskExecutor;
}

此时业务代码就不需要去设置子线程的值,直接使用即可

  • 代码
public void handlerAsync() {
       log.info("父线程的用户信息 -> {}", TtlParameterWrapper.get());
        //执行异步任务,需要指定的线程池
       CompletableFuture.runAsync(() -> 
           log.info("子线程的用户信息 -> {}", TtlParameterWrapper.get()
       ),taskExecutor);
}

这里使用的是CompletableFuture执行异步任务,使用@Async这个注解同样是可行的。

注意:无论使用何种方式,都需要指定线程池

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值