线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal

线程间数据传递之ThreadLocal、InheritableThreadLocal、TransmittableThreadLocal

1、ThreadLocal介绍

spring 中基于 ThreadLocal 来实现事务。

多线程 访问同一个共享变量的时候容易出现并发问题,ThreadLocal是除了加锁这种同步方式之外的一种保证

规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是

线程自己的变量这样就不会存在线程不安全问题。在实际多线程操作的时候,操作的是自己本地内存中的变量,从

而规避了线程安全问题。

ThreadLocal又叫做线程本地变量是为每一个Thread创建的一个变量的副本,每个线程都可以在内部访问到这个副

本。通过这种方式我们在一个线程的生命周期以内安全的访问这个变量,不用担心被其他线程所污染。这是它相对

于全局变量所带来的优势。

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class ThreadLocalDemo {

    private ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    private void fun1() {
        threadLocal.set(System.nanoTime());
        System.out.println("fun1:" + threadLocal.get());
        fun2();
    }

    private void fun2() {
        System.out.println("fun2:" + threadLocal.get());
        fun3();
    }

    private void fun3() {
        System.out.println("fun3:" + threadLocal.get());
        threadLocal.remove();
    }

    public static void main(String[] args) {
        ThreadLocalDemo demo = new ThreadLocalDemo();
        demo.fun1();
    }
}
# 程序输出
fun1:1511680153700
fun2:1511680153700
fun3:1511680153700

我们在先创建了一个本地变量threadLocal 在fun1中set值,在其余的方法中我们并没有通过方法传递的方式显示

的将值传递给其他方法,仅仅是通过threadLocal变量get的方式就可以获取到我们所需要的变量,从而实现了变量

的跨方法的传递。可能你觉得这样写没什么好处,我不用threadLocal直接用个全局变量照样可以实现数据的传

递,我们可以改造一下fun1方法。

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class ThreadLocalDemo1 {

    private ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    private void fun1() throws InterruptedException {
        threadLocal.set(System.nanoTime());
        System.out.println("fun1:" + threadLocal.get());
        final Thread t1 = new Thread(() -> {
            System.out.println("t1:" + threadLocal.get());
        }, "t1");
        t1.start();
        t1.join();
        fun2();
    }

    private void fun2() {
        System.out.println("fun2:" + threadLocal.get());
        fun3();
    }

    private void fun3() {
        System.out.println("fun3:" + threadLocal.get());
        threadLocal.remove();
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo1 threadLocalDemo1 = new ThreadLocalDemo1();
        threadLocalDemo1.fun1();
    }
}
# 程序输出
fun1:3554613378000
t1:null
fun2:3554613378000
fun3:3554613378000

按照常理来想那么我们t1线程内也应该能获取到数据,但是结果大相径庭。实际上 threadLocal 对象只是作为了一

个 key,而真正存储数据的是每个线程自身的 thread 内持有的一个 ThreadLocalMap 的对象,而我们的 t1 线程

自然就不能获取到数据。如果使用后不 remove 可能会有内存泄漏的风险!

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class ThreadLocalDemo2 {

    private ThreadLocal<Long> threadLocal = new ThreadLocal<>();

    public void run() {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    threadLocal.set(System.nanoTime());
                    Long aLong = threadLocal.get();
                    threadLocal.remove();
                    System.out.println(aLong);
                }
            }).start();
        }
    }

    public static void main(String[] args) {
        ThreadLocalDemo2 threadLocalDemo2 = new ThreadLocalDemo2();
        threadLocalDemo2.run();
    }
}
# 程序输出
3042907777600
3042907791300
3042908175100
3042908275900
3042908395900
3042908245300
3042908563900
3042908182800
3042908589200
3042908716700
package com.example.threadlocalandother;

/**
 * @author tom
 */
public class ThreadLocalDemo3 {

    /**
     * 创建ThreadLocal变量
     */
    private static final ThreadLocal<Integer> LOCAL_VARIABLE = new ThreadLocal<>();

    static void print(String str) {
        // 打印当前线程本地内存中localVariable变量的值
        System.out.println(str + ": " + LOCAL_VARIABLE.get());
        // 清除当前线程本地内存中的localVariable变量
        LOCAL_VARIABLE.remove();
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 设置线程1中本地变量的值
                LOCAL_VARIABLE.set(100);
                print("Thread 1");
                System.out.println("After remove: " + LOCAL_VARIABLE.get());
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                // 设置线程2中本地变量的值
                LOCAL_VARIABLE.set(200);
                print("Thread 2");
                System.out.println("After remove: " + LOCAL_VARIABLE.get());
            }
        });

        t1.start();
        t2.start();
    }
}
# 程序输出
Thread 1: 100
Thread 2: 200
After remove: null
After remove: null
package com.example.threadlocalandother;

/**
 * @author tom
 */
public class UserContext implements AutoCloseable {

    private final ThreadLocal<String> ctx = new ThreadLocal<>();

    public UserContext(String user) {
        ctx.set(user);
    }

    public String currentUser() {
        return ctx.get();
    }

    @Override
    public void close() {
        ctx.remove();
    }

    public static void main(String[] args) {
        try (UserContext userContext = new UserContext("Bob")) {
            // 可任意调用userContext.currentUser():
            String currentUser = userContext.currentUser();
            System.out.println(currentUser);
        } // 在此自动调用UserContext.close()方法释放ThreadLocal关联对象
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}
# 程序输出
Bob

使用ThreadLocal的最终目的还是为了得到安全的数据。

问题:同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的,好在InheritableThreadLocal

可以解决这个问题。

2、InheritableThreadLocal介绍

以我们在最开始的demo中t1线程获取不到数据。但是如果我们有这种诉求,希望父线程能够向子线程传递数据

呢,那我们便可以用到InheritableThreadLocal。

如何解决子线程获取父线程的数据就要使用InheritableThreadLocal。

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo {

    private ThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal<>();

    private void fun1() throws InterruptedException {
        inheritableThreadLocal.set(System.nanoTime());
        System.out.println("fun1:" + inheritableThreadLocal.get());
        final Thread t1 = new Thread(() -> {
            System.out.println("t1:" + inheritableThreadLocal.get());
        }, "t1");
        t1.start();
        t1.join();
        fun2();
    }

    private void fun2() {
        System.out.println("fun2:" + inheritableThreadLocal.get());
        fun3();
    }

    private void fun3() {
        System.out.println("fun3:" + inheritableThreadLocal.get());
        inheritableThreadLocal.remove();
    }

    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocalDemo inheritableThreadLocalDemo = new InheritableThreadLocalDemo();
        inheritableThreadLocalDemo.fun1();
    }
}
# 程序输出
fun1:7459897559400
t1:7459897559400
fun2:7459897559400
fun3:7459897559400

我们可以看到在t1线程中能够正确的获取到结果。

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo1 {

    private ThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();

    public void run() {
        //给父类inheritableThreadLocals赋值
        inheritableThreadLocal.set(1);
        new Thread(new Runnable() {
            @Override
            public void run() {
                //子线程同时去获取自己的inheritableThreadLocals
                Integer integer = inheritableThreadLocal.get();
                System.out.println(integer);
            }
        }).start();
        Integer integer = inheritableThreadLocal.get();
        System.out.println(integer);
        inheritableThreadLocal.remove();
    }

    public static void main(String[] args) {
        InheritableThreadLocalDemo1 inheritableThreadLocalDemo1 = new InheritableThreadLocalDemo1();
        inheritableThreadLocalDemo1.run();
    }
}
# 程序输出
1
1

InheritableThreadLocal类**「继承」**了ThreadLocal类,并重写了childValue、getMap、createMap方法。

总之,InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的

inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,

就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数

会将父线程的inheritableThreadLocals中的变量**「复制一份到子线程的inheritableThreadLocals」**变量中。

package com.example.threadlocalandother;

import com.alibaba.fastjson.JSONObject;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author tom
 */
public class UserInheritableThreadLocal {

    static InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void set(String key, String value) {
        Map<String, String> headers = new HashMap<>();
        headers.put(key, value);
        inheritableThreadLocal.set(headers);
    }

    public static Map<String, String> get() {
        return inheritableThreadLocal.get();
    }

    public static void remove() {
        if (inheritableThreadLocal != null) {
            inheritableThreadLocal.remove();
        }
    }

    public static void main(String[] args) {
        //设置值
        UserInheritableThreadLocal.set("name", "tom");
        Map<String, String> stringStringMap = UserInheritableThreadLocal.get();
        System.out.println(JSONObject.toJSONString(stringStringMap));
        // 创建线程池
        ExecutorService executorService = new ThreadPoolExecutor(5, 5, 1, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(5));
        //使用线程池创建子线程,并在子线程获取
        executorService.submit(() -> {
            Map<String, String> stringMap = UserInheritableThreadLocal.get();
            System.out.println(JSONObject.toJSONString(stringMap));
        });
        UserInheritableThreadLocal.remove();
        executorService.shutdown();
    }
}
# 程序输出
{"name":"tom"}
{"name":"tom"}
package com.example.threadlocalandother;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo2 {

    // 创建一个InheritableThreadLocal来存储用户ID
    private static final InheritableThreadLocal<String> userIdThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        // 模拟处理用户请求
        handleUserRequest("user123");
    }

    public static void handleUserRequest(String userId) {
        // 在处理请求前,设置当前线程的用户ID
        userIdThreadLocal.set(userId);
        try {
            // 模拟一些业务逻辑
            doBusinessLogic();
            // 模拟异步处理任务
            ExecutorService executor = Executors.newSingleThreadExecutor();
            executor.submit(() -> {
                // 在子线程中也可以访问到父线程设置的用户 ID
                System.out.println("Processing task in child thread for user: " + userIdThreadLocal.get());
            });
            executor.shutdown();
        } finally {
            // 清除当前线程的用户 ID,防止内存泄漏
            userIdThreadLocal.remove();
        }
    }

    public static void doBusinessLogic() {
        // 在业务逻辑中可以访问到当前线程的用户 ID
        System.out.println("Processing business logic for user: " + userIdThreadLocal.get());
    }
}
# 程序输出
Processing business logic for user: user123
Processing task in child thread for user: user123

需要在实际使用完毕的时候,及时调用remove方法避免内存泄漏。

inheritableThreadLocal是线程安全的吗?

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo3 {

    private ThreadLocal<Person> inheritablethreadlocal = new InheritableThreadLocal<>();

    private void fun1() throws InterruptedException {
        final Person person = new Person();
        person.setName("张三");
        inheritablethreadlocal.set(person);
        System.out.println("fun1:" + inheritablethreadlocal.get());
        final Thread t1 = new Thread(() -> {
            Person p = (Person) inheritablethreadlocal.get();
            p.setName("李四");
            System.out.println("t1:" + inheritablethreadlocal.get());
        }, "t1");
        t1.start();
        t1.join();
        fun2();
    }

    private void fun2() {
        System.out.println("fun2:" + inheritablethreadlocal.get());
        fun3();
    }

    private void fun3() {
        System.out.println("fun3:" + inheritablethreadlocal.get());
        inheritablethreadlocal.remove();
    }

    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocalDemo3 inheritableThreadLocalDemo3 = new InheritableThreadLocalDemo3();
        inheritableThreadLocalDemo3.fun1();
    }
}

class Person {

    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" + "name='" + name + '\'' + '}';
    }
}
# 程序输出
fun1:Person{name='张三'}
t1:Person{name='李四'}
fun2:Person{name='李四'}
fun3:Person{name='李四'}

如果它是线程安全的那么,在t1线程中的修改应该不能够影响到其他线程中的值,显而易见它并不是线程安全的。

解决方法是重写childValue方法,在里面做一个深拷贝就可以了。

package com.example.threadlocalandother;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo4 {

    private ThreadLocal<Person> inheritablethreadlocal = new InheritableThreadLocal() {
        @Override
        protected Object childValue(Object parentValue) {
            Person p = (Person) parentValue;
            final Person child = new Person();
            child.setName(p.getName());
            return child;
        }
    };

    private void fun1() throws InterruptedException {
        final Person person = new Person();
        person.setName("张三");
        inheritablethreadlocal.set(person);
        System.out.println("fun1:" + inheritablethreadlocal.get());
        final Thread t1 = new Thread(() -> {
            Person p = (Person) inheritablethreadlocal.get();
            p.setName("李四");
            System.out.println("t1:" + inheritablethreadlocal.get());
        }, "t1");
        t1.start();
        t1.join();
        fun2();
    }

    private void fun2() {
        System.out.println("fun2:" + inheritablethreadlocal.get());
        fun3();
    }

    private void fun3() {
        System.out.println("fun3:" + inheritablethreadlocal.get());
        inheritablethreadlocal.remove();
    }

    public static void main(String[] args) throws InterruptedException {
        InheritableThreadLocalDemo4 inheritableThreadLocalDemo4 = new InheritableThreadLocalDemo4();
        inheritableThreadLocalDemo4.fun1();
    }
}
# 程序输出
fun1:Person{name='张三'}
t1:Person{name='李四'}
fun2:Person{name='张三'}
fun3:Person{name='张三'}

看起来似乎是解决了问题,结果也很美好。那么进一步思考下,我们这是在父线程给子线程传值,如果是两个不相

关的线程呢,比如说在线程池中会怎么样呢?创建线程如果是用显示创建使用inheritableThreadLocals是没有问

题。但如果是使用线程池就会有问题。

package com.example.threadlocalandother;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo5 {

    static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
    static ThreadLocal<Integer> inheritableThreadLocal = new InheritableThreadLocal<>();

    public static void main(String[] args) {
        System.out.println("主线程开始");
        for (int i = 0; i < 10; i++) {
            inheritableThreadLocal.set(i);
            System.out.println("主线程获取值:" + inheritableThreadLocal.get());
            executorService.execute(new RunnableDemo());
            inheritableThreadLocal.remove();
        }
        executorService.shutdown();
    }

    private static class RunnableDemo implements Runnable {
        @Override
        public void run() {
            System.out.println("子线程获取值:" + inheritableThreadLocal.get());
        }
    }
}
# 程序输出
主线程开始
主线程获取值:0
主线程获取值:1
主线程获取值:2
主线程获取值:3
主线程获取值:4
子线程获取值:0
子线程获取值:1
主线程获取值:5
子线程获取值:1
子线程获取值:0
子线程获取值:1
主线程获取值:6
子线程获取值:0
子线程获取值:0
主线程获取值:7
主线程获取值:8
子线程获取值:0
子线程获取值:1
主线程获取值:9
子线程获取值:0

按照我们上文的分析,那也结果也应该是子线程跟主线程都会输出从0~9,但是结果却大相径庭。

package com.example.threadlocalandother;

import java.util.concurrent.*;

/**
 * @author tom
 */
public class InheritableThreadLocalDemo6 {

    static ThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
    static ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
    static CountDownLatch firstCountDownLatch = new CountDownLatch(1);
    static CountDownLatch secondCountDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set(1);
        executorService.execute(() -> {
            System.out.println("子线程获取" + threadLocal.get());
            threadLocal.remove();
            System.out.println("异步任务1");
            firstCountDownLatch.countDown();
        });
        firstCountDownLatch.await();
        System.out.println("父线程获取" + threadLocal.get());

        threadLocal.set(2);
        executorService.execute(() -> {
            System.out.println("子线程获取" + threadLocal.get());
            threadLocal.remove();
            System.out.println("异步任务2");
            secondCountDownLatch.countDown();
        });
        secondCountDownLatch.await();
        System.out.println("父线程获取" + threadLocal.get());
        executorService.shutdown();
    }
}
# 程序输出
子线程获取1
异步任务1
父线程获取1
子线程获取null
异步任务2
父线程获取2

我们新创建了一个Runnable对象放入线程池中通过execute方法执行,会首先判断线程池的核心线程数有没有达

到最大,如果还没达到最大那么新启动一个work线程,如果达到最大,那么接着判断我们给的队列是否满了,如

果还未满就入队,如果已经满了继续判断最大线程数是否达到最大,如果还未达到最大则继续新启动一个work线

程,已经达到最大就执行拒绝策略。

当核心线程数还没用完的时候,会创建新的线程,那么InheritableThreadLocal的值就会从父线程里面copy,自然

是没有问题,当我们在子线程中操作InheritableThreadLocal是可以拿到数据。

但是如果有新的任务进来(核心线程池满了,队列没有满),只要核心线程有空闲,就会复用原先创建好的核心线

程,这个时候,如果上一个使用过这个线程的子线程修改了InheritableThreadLocal,那么当前的子线程在使用

InheritableThreadLocal就会有问题了。因为这次是没有重行创建新线程,那么InheritableThreadLocal还是之前

的InheritableThreadLocal。

InheritableThreadLocal的继承性是在new Thread创建子线程时候在构造函数内把父线程内线程变量拷贝到子线

程内部的。为了不在创建新线程耗费资源,我们一般会用线程池,线程池的线程会复用,那么线程中的

ThreadLocal便不对了,可能是旧的,因为线程是旧的。

如果我们想在线程池等复用线程的组建中,使用ThreadLocal值的传递功能,来解决异步执行时上下文传递,那么

应该如何处理呢?

3、TransmittableThreadLocal介绍

JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行

组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已

经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时。

TransmittableThreadLocal类继承并加强InheritableThreadLocal类,解决上述的问题。

InheritableThreadLocal为阿里开源的一个组件,所以我们在使用的时候需要添加如下依赖:

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

示例:

package com.example.threadlocalandother;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author tom
 */
public class TransmittableThreadLocalDemo {

    static ExecutorService executorService = new ThreadPoolExecutor(2, 2, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
    static ThreadLocal<Integer> inheritableThreadLocal = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        System.out.println("主线程开始");
        for (int i = 0; i < 10; i++) {
            inheritableThreadLocal.set(i);
            System.out.println("主线程获取值:" + inheritableThreadLocal.get());
            executorService.execute(TtlRunnable.get(new RunnableDemo()));
            inheritableThreadLocal.remove();
        }
        executorService.shutdown();
    }

    private static class RunnableDemo implements Runnable {
        @Override
        public void run() {
            System.out.println("子线程获取值:" + inheritableThreadLocal.get());
        }
    }
}
# 程序输出
主线程开始
主线程获取值:0
主线程获取值:1
主线程获取值:2
主线程获取值:3
子线程获取值:0
子线程获取值:1
主线程获取值:4
子线程获取值:2
子线程获取值:3
主线程获取值:5
子线程获取值:4
子线程获取值:5
主线程获取值:6
主线程获取值:7
子线程获取值:6
主线程获取值:8
子线程获取值:7
主线程获取值:9
子线程获取值:8
子线程获取值:9

这样我们就可以在子线程中正确的获取到想要的结果了。

package com.example.threadlocalandother;

import com.alibaba.ttl.TransmittableThreadLocal;
import com.alibaba.ttl.TtlRunnable;

import java.util.concurrent.*;

/**
 * @author tom
 */
public class TransmittableThreadLocalDemo2 {

    static ThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();
    static ExecutorService executorService = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100));
    static CountDownLatch firstCountDownLatch = new CountDownLatch(1);
    static CountDownLatch secondCountDownLatch = new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set(1);
        executorService.execute(TtlRunnable.get(() -> {
            System.out.println("子线程获取" + threadLocal.get());
            threadLocal.remove();
            System.out.println("异步任务1");
            firstCountDownLatch.countDown();
        }));
        firstCountDownLatch.await();
        System.out.println("父线程获取" + threadLocal.get());

        threadLocal.set(2);
        //就算上面线程把数据删了,而且还复用了上面的线程,也不影响。
        executorService.execute(TtlRunnable.get(() -> {
            System.out.println("子线程获取" + threadLocal.get());
            threadLocal.remove();
            System.out.println("异步任务2");
            secondCountDownLatch.countDown();
        }));
        secondCountDownLatch.await();
        System.out.println("父线程获取" + threadLocal.get());
        executorService.shutdown();
    }
}
# 程序输出
子线程获取1
异步任务1
父线程获取1
子线程获取2
异步任务2
父线程获取2
package com.example.threadlocalandother;

import com.alibaba.ttl.TransmittableThreadLocal;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author tom
 */
public class TransmittableThreadLocalDemo3 {

    private static final TransmittableThreadLocal<String> threadLocal = new TransmittableThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(2);
        executorService.submit(() -> {
            threadLocal.set("Hello, World!");
            try {
                doBusinessLogic();
            } finally {
                threadLocal.remove();
            }
        });
        executorService.submit(() -> {
            try {
                doBusinessLogic();
            } finally {
                threadLocal.remove();
            }
        });
        executorService.shutdown();
    }

    public static void doBusinessLogic() {
        // 在业务逻辑中可以访问到当前线程的变量值
        System.out.println("Processing business logic with value: " + threadLocal.get());
    }

}
# 程序输出
Processing business logic with value: Hello, World!
Processing business logic with value: null

自己实现一个类似于TransmittableThreadLocal的功能:

package com.example.threadlocalandother;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;

/**
 * @author tom
 */
public class TransmittableThreadLocalDemo4 {

    private static ThreadLocal<String> contextHolder = new ThreadLocal<>();

    public static <T> CompletableFuture<T> invokeToCompletableFuture(Supplier<T> supplier) {
        // 第一步
        String context = contextHolder.get();
        Supplier<T> newSupplier = () -> {
            //第二步
            String origin = contextHolder.get();
            try {
                contextHolder.set(context);
                // 第三步
                return supplier.get();
            } finally {
                // 第四步
                contextHolder.set(origin);
            }
        };
        return CompletableFuture.supplyAsync(newSupplier);
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        contextHolder.set("main");
        CompletableFuture<String> context = invokeToCompletableFuture(() -> TransmittableThreadLocalDemo4.contextHolder.get());
        System.out.println(context.get());
    }
}
# 程序输出
main

总得来说,就是在将异步任务派发给线程池时,「对其做一下上下文传递的处理。」

第一步:主线程获取上下文,传递给任务暂存。之后的操作都将是异步执行线程操作的。

第二步:异步执行线程将原有上下文取出,暂时保存。并将主线程传递过来的上下文设置。

第三步:执行异步任务。

第四步:将原有上下文设置回去。

可以看到一般在异步线程执行完任务之后不会直接进行remove,而是一开始取出原上下文(可能为 「NULL」

也可能是线程创建时InheritableThreadLocal 继承过来的值。当然后续也会被清除的),并在任务执行

「结束重新放回」。这样的方式可以说是异步 「ThreadLocal 传递的标准范式」

这样子既起到了显式清除主线程带来的上下文,也避免了如果线程池的拒绝策略为 CallerRunsPolicy ,后续

处理时上下文丢失的问题。

  • 19
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值