TransmittableThreadLocal和ThreadPoolTaskExecutor

transmittable-thread-local

    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>transmittable-thread-local</artifactId>
      <version>2.14.2</version>
    </dependency>

ThreadLocal用于线程隔离,常用与纪录用户登陆状态、事务状态等。在链路追踪中,ThreadLocal也可用于记录TID,但通常遇到线程池时,即时使用InheritableThreadLocal (ITL)时TID无法被传递到执行线程中,归根结底是因为线程会被复用,而ITL只能在线程创建时继承父类线程的变量,而在任务执行时,父类这些变量可能已经变化了,这时是无法同步到ITL的。

PS: 一次「找回」TraceId的问题分析与过程思考

问题案例

import com.alibaba.ttl.TransmittableThreadLocal;
...
public class TTLTest{
    
    
	// 此时相当于一个普通的InheritableThreadLocal
	static TransmittableThreadLocal<String> ctx = new TransmittableThreadLocal<>();
	
	public static void main() throws InterruptedException {
    
    
        // 2个线程
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 初始设置
        ctx.set("1");
        System.out.println("set 1 success");

        BizRunnable bizRunnable = new BizRunnable();

        // 第一个线程创建,此时TTL当作ITL使用时新线程的ctx为1
        es.execute(bizRunnable);

        // 再次设置
        ctx.set("2");
        System.out.println("set 2 success");
        // 第二个线程创建,此时TTL当作ITL使用时新线程的ctx为2
        es.execute(bizRunnable);
        for (int i = 0; i < 20; i++) {
    
    
            // 模拟复用,第一个线程的ctx仍然是创建时的1而不是2
            es.execute(bizRunnable);
        }
    }
    public static class BizRunnable implements Runnable{
    
    
        @Override
        public void run() {
    
    
            String s = ctx.get();
            System.out.println("get : "+s);
        }
    }

}
 
运行结果:
set 1 success
set 2 success
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 1
get : 2
get : 2
get : 1
get : 2
get : 1

正确案例

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;
...
public TTLTest{
    
    

 	public static void main() throws InterruptedException {
    
    
        // 2个线程
        ExecutorService es = Executors.newFixedThreadPool(2);
        // 初始设置
        ctx.set("1");
        System.out.println("set 1 success");

        BizRunnable old = new BizRunnable();
        // com.alibaba.ttl.TtlRunnable 包装原来的Runnable
        TtlRunnable bizRunnable = TtlRunnable.get(old);
        // 第一个线程创建,此时TTL当作ITL使用时新线程的ctx为1
        es.execute(bizRunnable);

        // 改动无效,无法被传递到线程池线程
        ctx.set("2");
        System.out.println("set 2 success");
        // 第二个线程创建,此时TTL当作ITL使用时新线程的ctx为2
        // 第一个线程ctx同时被设为1
        // 每次调用都要重新包装
        bizRunnable = TtlRunnable.get(old);
        es.execute(bizRunnable);
        for (int i = 0; i < 20; i++) {
    
    
            // 模拟复用
            es.execute(bizRunnable);
        }
    }
    public static class BizRunnable implements Runnable{
    
    

        @Override
        public void run() {
    
    
            String s = ctx.get();
            System.out.println("get : "+s);
        }
    }

}

运行结果:
set 1 success
set 2 success
get : 1
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2
get : 2

TTL的使用就是包装提交的RunnableCallable或线程池,注意:包装RunnableCallable时,每次提交都要重新包装,这样很麻烦,可以直接包装线程池,或者使用Spring提供的ThreadPoolTaskExecutor,该类会对提交的Runnable进行包装:

     ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(8);
        executor.setCorePoolSize(3);
        executor.setThreadNamePrefix("prefix-");
        executor.setQueueCapacity(200);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        // 设置包装器
        executor.setTaskDecorator(TtlRunnable::get);
        executor.initialize();

猜你喜欢

转载自blog.csdn.net/weixin_41866717/article/details/130766802