性能文章>解密Transmittable-Thread-Local:让跨线程拷贝变得轻松自如>

解密Transmittable-Thread-Local:让跨线程拷贝变得轻松自如原创

1年前
356197

背景

Java作为一门广泛应用的编程语言,其在跨线程拷贝方面的处理一直是一个令人头疼的问题。在多线程的应用场景中,经常需要将某些数据从一个线程传递到另一个线程,比如如下几个场景:

  1. 全链路追踪:全链路追踪是一种用于监控和诊断分布式系统的技术。在全链路追踪中,需要将请求的上下文信息从一个服务传递到另一个服务,以便跟踪整个请求的处理过程。

  2. 灰度发布:在灰度发布中,需要将请求的标识信息从一个服务传递到另一个服务,以便根据标识信息判断是否应该对该请求进行灰度发布。

  3. 分布式事务:在分布式事务中,需要将事务上下文信息从一个服务传递到另一个服务,以便保证整个分布式事务的一致性。

  4. 异步编程:在异步编程中,可能需要将数据从一个线程传递到另一个线程,比如在一个请求处理完之后,需要将处理结果传递给另一个线程进行处理

然而,由于Java的内存模型和线程模型的特殊性,这种跨线程拷贝往往会带来很多问题。

其中最常见的问题就是数据不一致性。由于Java中的每个线程都有自己独立的内存空间,因此在进行跨线程拷贝时,很容易出现数据不一致的情况。比如,一个线程将一个对象的某个属性值修改了,但是在另一个线程中读取该对象时,却读取到了修改之前的值,这就会导致数据不一致。

为了解决这个问题,阿里开源了一种名为Transmittable-Thread-Local(简称TTL)的解决方案。这种方案可以让程序员在进行跨线程拷贝时,保证数据的一致性。

Transmittable-Thread-Local是一个Java库,它提供了一种特殊的ThreadLocal实现,可以在进行跨线程拷贝时将数据进行隐式传递。具体来说,当一个线程需要将某个数据传递给另一个线程时,它可以将这个数据放入Transmittable-Thread-Local中。当另一个线程需要获取这个数据时,它可以直接从Transmittable-Thread-Local中获取,而不需要进行显式的拷贝操作。这样就可以保证数据的一致性了。

使用Transmittable-Thread-Local有很多好处。首先,它可以简化代码编写。由于数据传递是隐式进行的,程序员不需要显式地将数据进行拷贝操作,因此代码会更加简洁清晰。其次,它可以提高程序的性能。由于不需要进行显式的拷贝操作,因此可以减少内存的使用和GC的开销,从而提高程序的性能。

目前,Transmittable-Thread-Local已经成为了Java中跨线程拷贝的标准解决方案之一。在很多大型项目中都得到了广泛应用。比如,在阿里巴巴的分布式系统中,就广泛使用了Transmittable-Thread-Local来保证数据的一致性和程序的性能。对于那些需要在多线程应用中进行跨线程数据传递的程序员来说,它是一个不可或缺的工具。因此,无论是开发应用还是解决实际中的痛点问题,掌握Transmittable-Thread-Local都是非常重要的。

是什么

TransmittableThreadLocal是一个Java库,用于在跨线程传递时,保持线程本地变量的值不变。简单来说,它可以让你在不同的线程间传递线程本地变量的值,而不需要手动进行传递。

TransmittableThreadLocal是一种线程本地变量扩展类,它可以在线程池等场景下,将线程本地变量的值跨线程传递。这个类最初由阿里巴巴开发,在2015年发布,并在GitHub上开源。

架构说明

Transmittable-Thread-Local (TTL) 是一个 Java 库,它提供了一种机制来实现跨线程拷贝。它通过在线程之间传递数据来实现这一点。当一个线程创建一个 TTL 变量时,这个变量会被存储在一个共享变量 map 中。当这个线程启动其他线程时,TTL 库会将这个共享变量 map 传递给新的线程。新线程可以从共享变量 map 中获取之前创建的 TTL 变量的值,并在自己的线程中使用。这个共享变量依赖于创建线程对象时的InheritableThreadLocal对象具体见Thread类型的代码如下图所示:

主线程传递变量到子线程

子线程取值:

来自官网的核心流程图:

TTL 库的实现依赖于 Java 的 InheritableThreadLocal 类。当一个线程启动另一个线程时,InheritableThreadLocal 类会将父线程的 InheritableThreadLocal 变量复制到子线程中。TTL 库通过扩展 InheritableThreadLocal 类来实现跨线程拷贝。它在 InheritableThreadLocal 类的基础上添加了一些额外的功能,以便能够在不同线程之间传递 TTL 变量的值。

主要包括以下几个部分:

  1. TransmittableThreadLocal类:这是TransmittableThreadLocal的核心类,它继承了ThreadLocal类,并重写了一些方法,从而实现了线程本地变量的跨线程传递。

  2. Transmitter类:这是TransmittableThreadLocal的传输器类,它用于在线程之间传输线程本地变量的值。当一个线程需要将线程本地变量的值传递给另一个线程时,它会将变量值和相关信息封装成一个Transmitter对象,并发送给目标线程。

  3. Receiver类:这是TransmittableThreadLocal的接收器类,它用于接收其他线程发送的Transmitter对象,并将其还原成线程本地变量的值。当一个线程接收到一个Transmitter对象时,它会将Transmitter对象解析出来,并将其中的变量值存储到自己的线程本地变量中。

  4. TransmittableThreadLocalMap类:这是TransmittableThreadLocal的映射表类,它用于存储线程本地变量的值。每个线程都有自己的一个TransmittableThreadLocalMap对象,用于存储该线程的所有线程本地变量。

总体来说,TransmittableThreadLocal的架构比较复杂,但其核心思想是通过传输器和接收器来实现线程本地变量的跨线程传递。这种机制可以有效地解决线程池等场景下线程本地变量共享的问题,使得程序更加健壮和可靠。

入门案例

下面是一个简单的入门案例,演示如何使用TransmittableThreadLocal进行线程本地变量的跨线程传递:

首先来引入依赖:

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

示例代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import com.alibaba.ttl.TransmittableThreadLocal;

public class TransmittableThreadLocalDemo {
    private static TransmittableThreadLocal<String> ttl = new TransmittableThreadLocal<>();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2);

        for (int i = 0; i < 2; i++) {
            executorService.execute(() -> {
                ttl.set("hello");
                System.out.println(Thread.currentThread().getName() + ":" + ttl.get());
                executorService.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + ":" + ttl.get());
                });
            });
        }
    }
}

运行结果:

在这个案例中,我们首先创建了一个TransmittableThreadLocal对象ttl,然后创建了一个线程池executorService,并向其中提交了两个任务。在每个任务中,我们首先通过ttl.set()方法设置了线程本地变量的值为"hello",然后通过ttl.get()方法获取了该值,并打印到控制台上。

注意,在第二个任务中,我们又向线程池中提交了一个子任务。这个子任务也可以访问到父任务中设置的线程本地变量的值,这是因为TransmittableThreadLocal会自动将父线程中的变量值传递给子线程。

最后,我们调用executorService.shutdown()方法关闭线程池。

优点

使用TransmittableThreadLocal进行线程本地变量的跨线程传递,有以下几个优点:

  1. 避免了线程之间的竞争和冲突:在多线程编程中,线程之间的竞争和冲突是一个常见的问题。使用TransmittableThreadLocal可以让每个线程都有自己的变量副本,从而避免了线程之间的竞争和冲突。

  2. 提高了程序的性能:在一些特殊的场景中,比如使用线程池时,由于线程的复用,使用普通的ThreadLocal会导致变量值在不同的线程之间被共享,从而出现错误的结果。而使用TransmittableThreadLocal可以解决这个问题,从而提高了程序的性能。

  3. 简化了程序的编写:使用TransmittableThreadLocal可以让程序员更加简单地编写多线程程序,从而提高了开发效率。

  4. 可以应对更复杂的场景:TransmittableThreadLocal支持线程池等复杂场景下的线程本地变量传递,可以应对更加复杂的业务需求。

缺点

使用TransmittableThreadLocal进行线程本地变量的跨线程传递,也存在以下几个缺点:

  1. 增加了程序的复杂性:使用TransmittableThreadLocal需要对线程本地变量的使用方式进行调整,增加了程序的复杂性。

  2. 可能导致内存泄漏:由于TransmittableThreadLocal会在每个线程中创建一个副本,如果不及时清理,可能会导致内存泄漏。

  3. 不支持传递非序列化对象:TransmittableThreadLocal只支持传递序列化对象,如果需要传递非序列化对象,需要自己实现序列化和反序列化的逻辑。

  4. 可能导致死锁:在一些特殊的场景下,使用TransmittableThreadLocal可能会导致死锁问题。比如,在一个线程中获取了TransmittableThreadLocal的值,然后在另一个线程中等待这个值被设置后才能继续执行,这样就可能导致死锁问题。

综上所述,使用TransmittableThreadLocal需要根据具体的场景进行选择,权衡其优缺点,从而选择最合适的方案。

关注微信公众号  《中间件源码》   订阅更多内容。

 

点赞收藏
宋小生
请先登录,查看9条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步

为你推荐

Redis stream 用做消息队列完美吗?

Redis stream 用做消息队列完美吗?

Netty源码解析:writeAndFlush

Netty源码解析:writeAndFlush

7
9