使用TransmittableThreadLocal弥补InheritableThreadLocal的不足,可动态刷新线程变量

众所周知,使用InheritableThreadLocal可以实现父子线程之间的值传递,不过使用InheritableThreadLocal有一个隐藏的问题,子线程在创建的时候会从父线程InheritableThreadLocal中拷贝数据,一旦子线程创建成功以后,父线程更新了数据,对子线程是无效的,这种情况在使用线程池的时候会有数据不安全的问题,下面使用代码演示一下:

public class ITLTest {
    // 声明一个线程池
    private static final ExecutorService pool = Executors.newFixedThreadPool(2);
    // 声明一个InheritableThreadLocal全局变量
    private static final ThreadLocal<String> tl = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        // 设置一个值
        tl.set("张三");

        System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        // 声明一个Runnable对象
        Runnable r1 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        };
        // 向线程池提交
        pool.submit(r1);
        // 声明一个Callable对象
        Callable<String> c1 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
            return "success";
        };
        // 向线程池提交
        pool.submit(c1);

        tl.set("李四"); // 更新tl中的值
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "--->" + tl.get());

        // 声明一个Runnable对象
        Runnable r2 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        };
        // 向线程池提交
        pool.submit(r2);
        // 声明一个Callable对象
        Callable<String> c2 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
            return "success";
        };
        // 向线程池提交
        pool.submit(c2);

        // 关闭线程池
        pool.shutdown();
    }
}

运行结果:

main--->张三
pool-1-thread-1--->张三
pool-1-thread-2--->张三
main--->李四
pool-1-thread-1--->张三
pool-1-thread-2--->张三

可以看到,虽然主线程更新了InheritableThreadLocal中的值,但是子线程依然使用的是旧值,这样就不能保证数据的安全性了。

下面使用TransmittableThreadLocal改造一下,TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。

导入maven依赖:

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

TransmittableThreadLocal需要配合一些方法的使用,下面详细介绍一下使用方法:

  • 针对单个线程,需要配合TtlRunnable 或TtlCallable使用:
public class TTLTest2 {
    // 声明一个线程池
    private static final ExecutorService pool = Executors.newFixedThreadPool(2);
    // 声明一个TransmittableThreadLocal全局变量
    private static final ThreadLocal<String> tl = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        // 设置一个值
        tl.set("张三");

        System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        // 声明一个Runnable对象
        Runnable r1 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        };
        // 使用TtlRunnable包装一下Runnable对象
        TtlRunnable ttlRunnable1 = TtlRunnable.get(r1);
        // 向线程池提交
        pool.submit(ttlRunnable1);
        // 声明一个Callable对象
        Callable<String> c1 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
            return "success";
        };
        // 使用TtlCallable包装一下Callable对象
        TtlCallable<String> ttlCallable1 = TtlCallable.get(c1);
        // 向线程池提交
        pool.submit(ttlCallable1);

        tl.set("李四"); // 更新tl中的值
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "--->" + tl.get());

        // 声明一个Runnable对象
        Runnable r2 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        };
        // 使用TtlRunnable包装一下Runnable对象
        TtlRunnable ttlRunnable2 = TtlRunnable.get(r2);
        // 向线程池提交
        pool.submit(ttlRunnable2);
        // 声明一个Callable对象
        Callable<String> c2 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
            return "success";
        };
        // 使用TtlCallable包装一下Callable对象
        TtlCallable<String> ttlCallable2 = TtlCallable.get(c2);
        // 向线程池提交
        pool.submit(ttlCallable2);

        // 关闭线程池
        pool.shutdown();
    }
}

运行结果:

main--->张三
pool-1-thread-1--->张三
pool-1-thread-2--->张三
main--->李四
pool-1-thread-1--->李四
pool-1-thread-2--->李四

可以看到,主线程更新值以后,子线程中的值也更新了,可是这种方法不免有些太过麻烦,每声明一个线程都要包装一下,对此,可以针对整个线程池进行包装。

  • 针对整个线程池,需要配合TtlExecutors使用:
public class TTLTest {
    // 声明一个线程池
    private static final ExecutorService pool = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(2));
    // 声明一个TransmittableThreadLocal全局变量
    private static final ThreadLocal<String> tl = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        // 设置一个值
        tl.set("张三");

        System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        // 声明一个Runnable对象
        Runnable r1 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        };
        // 向线程池提交
        pool.submit(r1);
        // 声明一个Callable对象
        Callable<String> c1 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
            return "success";
        };
        // 向线程池提交
        pool.submit(c1);

        tl.set("李四"); // 更新tl中的值
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName() + "--->" + tl.get());

        // 声明一个Runnable对象
        Runnable r2 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
        };
        // 向线程池提交
        pool.submit(r2);
        // 声明一个Callable对象
        Callable<String> c2 = () -> {
            System.out.println(Thread.currentThread().getName() + "--->" + tl.get());
            return "success";
        };
        // 向线程池提交
        pool.submit(c2);

        // 关闭线程池
        pool.shutdown();
    }
}

运行结果:

main--->张三
pool-1-thread-1--->张三
pool-1-thread-2--->张三
main--->李四
pool-1-thread-1--->李四
pool-1-thread-2--->李四

可以看到,使用TtlExecutors.getTtlExecutorService方法包装线程池对象以后,主线程更新值以后,子线程也动态更新了。

上一篇:hive如何将split切割后的结果转成列输出


下一篇:18进阶、TLC语言