J.U.C Review - Java线程间的通信

在这里插入图片描述


Java线程间的通信

无锁的程序

public class NoneLock {

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread A " + i);
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                System.out.println("Thread B " + i);
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new ThreadA()).start();
        new Thread(new ThreadB()).start();
    }
}

线程A和线程B各自独立工作,输出自己的打印值。

在这里插入图片描述

现在有一个需求,我想等A先执行完之后,再由B去执行,怎么办呢?最简单的方式就是使用一个“对象锁”


锁与同步

在Java中,锁(Lock)是用来保护资源的。当一个线程拿到锁后,其他线程就必须等待,直到这个线程释放锁。我们可以用婚姻来比喻:一个锁一次只能被一个线程“持有”或“结婚”,其他线程必须等待,直到这个锁被“释放”或“离婚”。

为了确保多个线程能按顺序执行,我们可以使用锁来实现线程同步。比如,如果我们不使用锁,两个线程可能会在控制台同时输出,导致结果是混乱的。但如果我们希望线程A先完成,线程B再开始,我们就可以用锁来确保顺序。

示例代码:

public class ObjectLock {
    private static Object lock = new Object();

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread A " + i);
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 100; i++) {
                    System.out.println("Thread B " + i);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        // 在主线程里使用sleep方法睡眠了10毫秒,是为了防止线程B先得到锁。因为如果同时start,线程A和线程B都是出于就绪状态,操作系统可能会先让B运行。这样就会先输出B的内容,然后B执行完成之后自动释放锁,线程A再执行
        Thread.sleep(10);
        new Thread(new ThreadB()).start();
    }
}

在这个代码中,声明了一个名字为lock的对象锁。我们在ThreadAThreadB内需要同步的代码块里,都是用synchronized关键字加上了同一个对象锁lock. 线程A和线程B都使用同一个对象锁lock。当一个线程持有锁时,另一个线程必须等待锁被释放。这样,我们就确保了线程A和线程B不会同时执行。


等待/通知机制

锁虽然能确保线程同步,但可能会浪费资源,因为线程需要不停尝试获取锁。Java提供了另一种方式:等待/通知机制。

Java多线程的等待/通知机制是基于Object类的wait()方法和notify(), notifyAll()方法来实现的。

notify()方法会随机叫醒一个正在等待的线程,而notifyAll()会叫醒所有正在等待的线程。

一个锁同一时刻只能被一个线程持有。而假如线程A现在持有了一个锁lock并开始执行,它可以使用lock.wait()让自己进入等待状态。这个时候,lock这个锁是被释放了的。

这时,线程B获得了lock这个锁并开始执行,它可以在某一时刻,使用lock.notify(),通知之前持有lock锁并进入等待状态的线程A,说“线程A你不用等了,可以往下执行了”。

需要注意的是,这个时候线程B并没有释放锁lock,除非线程B这个时候使用lock.wait()释放锁,或者线程B执行结束自行释放锁,线程A才能得到lock锁。

代码示例:

public class WaitAndNotify {
    private static Object lock = new Object();

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadA: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            synchronized (lock) {
                for (int i = 0; i < 5; i++) {
                    try {
                        System.out.println("ThreadB: " + i);
                        lock.notify();
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                lock.notify();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(1000);
        new Thread(new ThreadB()).start();
    }
}

在这里插入图片描述

在这个例子中,线程A和线程B交替执行。每次一个线程执行完后都会使用notify()唤醒另一个线程,然后自己使用wait()方法陷入等待并释放lock


信号量

JDK提供了一个类似于“信号量”功能的类Semaphore。这里我们介绍一种基于volatile关键字的自己实现的信号量通信。

信号量是一种用于控制多个线程访问公共资源的机制。我们可以使用volatile关键字来实现简单的信号量。volatile保证变量的更新对其他线程可见。

volatile关键字能够保证内存的可见性,如果用volatile关键字声明了一个变量,在一个线程里面改变了这个变量的值,那其它线程是立马可见更改后的值的。

代码示例:

public class Signal {
    private static volatile int signal = 0;

    static class ThreadA implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 0) {
                    System.out.println("threadA: " + signal);
                    signal++;
                }
            }
        }
    }

    static class ThreadB implements Runnable {
        @Override
        public void run() {
            while (signal < 5) {
                if (signal % 2 == 1) {
                    System.out.println("threadB: " + signal);
                    signal++;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new ThreadA()).start();
        Thread.sleep(1000);
        new Thread(new ThreadB()).start();
    }
}

在这里插入图片描述

signal变量控制线程A和线程B的交替执行。线程A在signal为偶数时执行,线程B在signal为奇数时执行。

我们使用了一个volatile变量signal来实现了“信号量”的模型。这里需要注意的是,volatile变量需要进行原子操作。

需要注意的是,signal++并不是一个原子操作,所以在实际开发中,会根据需要使用synchronized给它“上锁”,或者是使用AtomicInteger等原子类。并且上面的程序也并不是线程安全的,因为执行while语句后,可能当前线程就暂停等待时间片了,等线程醒来,可能signal已经大于等于5了


管道

管道是一种线程间通信的方式,主要用于I/O流。

JDK提供了PipedWriterPipedReaderPipedOutputStreamPipedInputStream。其中,前面两个是基于字符的,后面两个是基于字节流的。

Java提供了基于字符和字节的管道流,我们可以使用这些管道在线程之间传递数据。

示例代码:

package com.artisan.thread;

import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;

/**
 * 定义Pipe类,用于演示PipedInputStream和PipedOutputStream如何进行数据传输
 */
public class Pipe {

    /**
     * 定义ReaderThread类,负责从PipedReader中读取数据
     */
    static class ReaderThread implements Runnable {
        private PipedReader reader;

        /**
         * 构造函数,初始化PipedReader
         *
         * @param reader PipedReader对象
         */
        public ReaderThread(PipedReader reader) {
            this.reader = reader;
        }

        /**
         * 实现Runnable接口的run方法,在新线程中执行读取操作
         */
        @Override
        public void run() {
            System.out.println("this is reader");
            int receive = 0;
            try {
                // 循环读取数据,直到没有数据可读
                while ((receive = reader.read()) != -1) {
                    System.out.print((char) receive);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 定义WriterThread类,负责向PipedWriter中写入数据
     */
    static class WriterThread implements Runnable {

        private PipedWriter writer;

        /**
         * 构造函数,初始化PipedWriter
         *
         * @param writer PipedWriter对象
         */
        public WriterThread(PipedWriter writer) {
            this.writer = writer;
        }

        /**
         * 实现Runnable接口的run方法,在新线程中执行写入操作
         */
        @Override
        public void run() {
            System.out.println("this is writer");
            int receive = 0;
            try {
                // 向PipedWriter写入测试数据
                writer.write("test");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // 关闭PipedWriter
                try {
                    writer.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 主函数,用于启动ReaderThread和WriterThread的示例
     */
    public static void main(String[] args) throws IOException, InterruptedException {
        PipedWriter writer = new PipedWriter();
        PipedReader reader = new PipedReader();
        // 确保PipedWriter和PipedReader连接,以便数据可以传输
        writer.connect(reader);

        // 启动ReaderThread
        new Thread(new ReaderThread(reader)).start();
        // 等待一段时间后启动WriterThread,以确保线程启动顺序
        Thread.sleep(1000);
        // 启动WriterThread
        new Thread(new WriterThread(writer)).start();
    }
}

输出

this is reader
this is writer
test
  1. 线程ReaderThread开始执行,
  2. 线程ReaderThread使用管道reader.read()进入”阻塞“,
  3. 线程WriterThread开始执行,
  4. 线程WriterThread用writer.write(“test”)往管道写入字符串,
  5. 线程WriterThread使用writer.close()结束管道写入,并执行完毕,
  6. 线程ReaderThread接受到管道输出的字符串并打印,
  7. 线程ReaderThread执行完毕。

ReaderThread从管道中读取数据,而WriterThread则向管道中写入数据。管道用于在两个线程之间传递信息。


其它通信相关

除了上面介绍的通信方式,还有一些其他的方法可以用来实现线程间的通信。

join方法

join()方法可以让一个线程等待另一个线程执行完成。例如,如果主线程希望等子线程执行完后再继续执行,就可以使用join()方法。

示例代码:

package com.artisan.thread;

/**
 * Join类用于演示Thread类的join方法的使用
 */
public class Join {

    /**
     * ThreadA是一个实现了Runnable接口的线程类
     * 它在run方法中模拟了一个长时间运行的任务
     */
    static class ThreadA implements Runnable {

        /**
         * run方法定义了线程执行时的行为
         * 它模拟了一个子线程,在运行时先睡眠1秒,然后醒来
         */
        @Override
        public void run() {
            try {
                System.out.println("我是子线程,我先睡一秒");
                Thread.sleep(1000); // 子线程睡眠1秒,模拟长时间运行的任务
                System.out.println("我是子线程,我睡完了一秒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * main方法是程序的入口
     * 它创建并启动了一个ThreadA线程,并使用join方法确保主线程等待子线程执行完毕后再继续执行
     * @param args 命令行参数
     * @throws InterruptedException 如果主线程等待被中断
     */
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new ThreadA()); // 创建子线程
        thread.start(); // 启动子线程
        thread.join(); // 确保主线程等待子线程执行完毕后再继续
        System.out.println("如果不加join方法,我会先被打出来,加了就不一样了");
    }
}


输出

我是子线程,我先睡一秒
我是子线程,我睡完了一秒
如果不加join方法,我会先被打出来,加了就不一样了

join 方法概述

  • join(long millis): 使当前线程等待调用此方法的线程终止,最长等待时间由 millis 参数指定,单位为毫秒。
  • join(long millis, int nanos): 除了接受一个毫秒数外,还接受一个额外的纳秒数(范围在0到999999之间),用于更细粒度的时间控制。但需注意,这个纳秒参数并不能保证绝对的精度。

底层实现细节

不论是 join(long) 还是 join(long, int),它们实际上都依赖于对象的监视器(monitor)来实现等待逻辑,具体是通过调用 Object 类的 wait(long timeout) 方法或其变体。这意味着调用 join 的线程会释放它当前持有的任何锁,并进入等待状态,直到目标线程执行完毕或者超时。

关于 join(long, int) 方法中的纳秒参数处理:JDK 实现中确实没有直接按纳秒精度去精确控制等待时间,这部分更多是一个兼容性设计,以保持 API 的一致性和向后兼容性。实际上,这个纳秒值会被合并到毫秒数中进行计算,然后传递给底层的等待方法,这导致了纳秒级别的精度无法直接体现。

小结

join 方法提供了让一个线程等待另一个线程完成的能力,主要通过两个重载形式实现不同时间精度的等待控制。尽管存在纳秒级别的参数输入,实际精度受限于 Java 虚拟机对线程调度和监控器操作的实现,通常并不保证达到纳秒级的精确控制。这些方法的底层机制涉及线程间的同步与等待,是通过监视器对象和 wait/notify 机制实现的。


sleep方法

sleep()方法让当前线程暂停一段时间。它是Thread类的静态方法,常用于模拟延迟或暂停执行。

需要注意的是,sleep()方法不会释放锁,而wait()方法会。


1. Thread.sleep 方法详解

Thread.sleepThread 类的一个静态方法,用于让当前线程暂停执行一段指定的时间。它有两个重载版本:

  • Thread.sleep(long millis):使当前线程暂停执行指定的毫秒数 (millis)。
  • Thread.sleep(long millis, int nanos):使当前线程暂停执行指定的毫秒数 (millis) 加上指定的纳秒数 (nanos)。

注意: sleep 方法并不精确到纳秒。JDK 1.8 的实现中,Thread.sleep(long millis, int nanos) 通过对纳秒数 (nanos) 的简单处理,仍然调用的是 Thread.sleep(long millis),即实际暂停时间是一个接近于(但不一定精确)的值。

2. sleepwait 的主要区别

1. 锁的释放:

  • Thread.sleep 方法不会释放当前线程持有的锁。这意味着如果线程在一个同步块或同步方法中调用了 sleep,其他线程无法获得这个锁,直到该线程重新获得CPU资源并退出同步块或同步方法。

  • Object.wait 方法会释放当前线程持有的锁。这意味着调用 wait 的线程会放弃锁,允许其他线程进入同步块或同步方法。线程会进入等待状态,直到被其他线程通过 notifynotifyAll 唤醒,并重新获得锁。

2. CPU资源的释放:

  • 两者都会释放 CPU 资源,意味着这段时间内不会执行任何代码。

  • wait 方法会将线程放入对象的等待池中,并且必须配合 notifynotifyAll 来唤醒。

  • sleep 只是单纯地让线程休眠一段时间,时间到后自动进入可运行状态(Runnable)。

3. 使用位置和使用场景:

  • Thread.sleep 可以在任意位置调用,并不要求在同步块或同步方法中。它是一个线程控制的方法,常用于在多线程编程中制造延迟。

  • Object.wait 必须在同步块或同步方法中调用。这是因为 wait 需要当前线程持有对象的锁,它通常用于线程间的通信。

3. 实际代码示例

Thread.sleep 示例:

public class SleepExample {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("Thread is going to sleep");
            try {
                Thread.sleep(2000);  // 睡眠2秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Thread has woken up");
        });
        thread.start();
    }
}

Object.wait 示例:

public class WaitExample {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 1 is waiting");
                try {
                    lock.wait();  // 线程1等待,释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 1 is resumed");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2 is notifying");
                lock.notify();  // 唤醒等待的线程1
            }
        });

        thread1.start();
        try {
            Thread.sleep(1000);  // 确保thread1先启动并等待
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread2.start();
    }
}

在这个例子中,thread1lock 对象上调用了 wait(),使其进入等待状态并释放锁。随后,thread2 在相同的 lock 对象上调用了 notify(),唤醒了 thread1


ThreadLocal类

ThreadLocal为每个线程提供独立的变量副本,确保各线程互不干扰。它常用于保存线程独立的数据,如数据库连接或Session。

package com.artisan.thread;
public class ThreadLocalDemo {

    // 实现Runnable接口的内部类ThreadA,用于演示ThreadLocal的使用
    static class ThreadA implements Runnable {
        private ThreadLocal<String> threadLocal;

        // 构造函数,初始化ThreadLocal实例
        public ThreadA(ThreadLocal<String> threadLocal) {
            this.threadLocal = threadLocal;
        }

        @Override
        public void run() {
            // 在当前线程中,为ThreadLocal变量设置一个值
            threadLocal.set("A");
            try {
                // 让线程睡眠1秒,模拟一段时间内的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 打印当前线程中ThreadLocal变量的值
            System.out.println("ThreadA输出:" + threadLocal.get());
        }
    }

    // 实现Runnable接口的内部类ThreadB,与ThreadA类似,但设置不同的值
    static class ThreadB implements Runnable {
        private ThreadLocal<String> threadLocal;

        // 构造函数,初始化ThreadLocal实例
        public ThreadB(ThreadLocal<String> threadLocal) {
            this.threadLocal = threadLocal;
        }

        @Override
        public void run() {
            // 在当前线程中,为ThreadLocal变量设置一个值
            threadLocal.set("B");
            try {
                // 让线程睡眠1秒,模拟一段时间内的操作
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 打印当前线程中ThreadLocal变量的值
            System.out.println("ThreadB输出:" + threadLocal.get());
        }
    }

    // 主函数,创建ThreadLocal实例,并启动两个使用该实例的线程
    public static void main(String[] args) {
        // 创建ThreadLocal实例,用于在不同线程中传递数据
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        // 创建并启动ThreadA实例所在的线程
        new Thread(new ThreadA(threadLocal)).start();
        // 创建并启动ThreadB实例所在的线程
        new Thread(new ThreadB(threadLocal)).start();
    }
}

输出

ThreadB输出:B
ThreadA输出:A

ThreadLocal 的基本概念

ThreadLocal 是 Java 中提供的一种机制,用于在多线程环境下实现线程局部变量。简单来说,ThreadLocal 为每个线程提供了一个独立的变量副本,这意味着每个线程都可以独立地访问和修改自己的副本,而不会影响其他线程的副本。

  • 线程局部变量:每个线程都有自己的 ThreadLocal 变量副本,其他线程无法访问或修改这个副本。
  • 隔离性ThreadLocal 通过提供线程专属的变量副本,确保了多线程环境中的数据隔离,避免了线程间的数据冲突。

ThreadLocal 的主要方法

  • set(T value): 设置当前线程的 ThreadLocal 变量的值。
  • get(): 获取当前线程的 ThreadLocal 变量的值。如果当前线程没有设置过值,且 ThreadLocal 变量有初始值,会返回初始值。
  • remove(): 删除当前线程的 ThreadLocal 变量值,释放资源。

ThreadLocal 的使用场景

ThreadLocal 常用于在多线程编程中需要保持线程独立状态的场景。典型的使用场景包括:

  1. 用户会话管理:在 Web 应用中,每个请求都可能在不同的线程中处理,可以使用 ThreadLocal 来存储每个线程独立的用户会话信息。

  2. 数据库连接:在多线程环境下,通过 ThreadLocal 可以为每个线程创建一个独立的数据库连接,避免线程间的资源冲突。

  3. 事务管理:当处理事务时,ThreadLocal 可以用来存储每个线程的事务上下文信息,确保线程独立处理事务。

示例代码

以下是一个简单的 ThreadLocal 使用示例:

public class ThreadLocalDemo {
    private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(() -> 0);

    public static void main(String[] args) {
        Runnable task = () -> {
            int value = threadLocal.get();
            value += 1;
            threadLocal.set(value);
            System.out.println(Thread.currentThread().getName() + ": " + threadLocal.get());
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");

        thread1.start();
        thread2.start();
    }
}

输出示例:

Thread 1: 1
Thread 2: 1

在这个示例中,threadLocal 为每个线程维护了一个独立的整数副本。即使它们使用了同一个 ThreadLocal 实例,两个线程仍然拥有独立的值。

为什么使用 ThreadLocal

在某些情况下,使用 ThreadLocal 是更为优雅和方便的解决方案。例如:

  • 避免参数传递的复杂性:当多个方法或类需要共享某个变量时,通过 ThreadLocal 可以隐式地传递该变量,而不需要在每个方法中显式地传递参数。

  • 确保线程安全:在并发环境中,ThreadLocal 可以确保每个线程操作的变量是独立的,从而避免了线程安全问题。

注意事项

  • 内存泄漏:如果没有在适当的时机调用 remove() 方法,ThreadLocal 可能会导致内存泄漏,特别是在使用线程池的情况下,因为线程池中的线程是重用的。

小结

ThreadLocal 是一种为每个线程提供独立变量副本的机制,用于在多线程环境中实现线程间数据隔离。它简化了线程独立状态的管理,常用于会话管理、数据库连接等场景,但使用时需注意内存管理和合理性。


InheritableThreadLocal

基本概念

InheritableThreadLocal 是 Java 中的一个特殊版本的 ThreadLocal,用于在子线程中继承父线程的 ThreadLocal 变量值。与普通的 ThreadLocal 不同,InheritableThreadLocal 允许一个线程的子线程访问父线程中已经设置的本地变量副本。

  • 继承机制:当一个线程创建了子线程时,子线程会自动继承父线程中 InheritableThreadLocal 的值。这使得子线程可以共享父线程的上下文信息,例如用户身份、事务上下文等。

  • ThreadLocal 的区别:普通的 ThreadLocal 变量在子线程中是不可见的,而 InheritableThreadLocal 使得子线程能够访问父线程在 InheritableThreadLocal 中设置的值。

InheritableThreadLocal 的使用场景

InheritableThreadLocal 主要用于在父线程和子线程之间传递一些需要共享的上下文信息,例如:

  1. 用户身份信息:在父线程中保存用户身份信息,子线程可以直接访问而不需要显式传递。

  2. 事务管理:在父线程中开始一个事务,并在子线程中继续使用相同的事务上下文。

  3. 日志跟踪:在多线程环境中,传递和记录与父线程相关的上下文信息,如日志中的追踪ID。

示例代码

package com.artisan.thread;

/**
 * InheritableThreadLocalDemo 类演示了如何使用 InheritableThreadLocal
 * 在父线程和子线程之间传递值
 */
public class InheritableThreadLocalDemo {
    // 创建一个 InheritableThreadLocal 对象,用于在不同线程之间传递字符串值
    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();

    /**
     * 程序的入口点
     * 在主线程中设置 InheritableThreadLocal 的值,并启动一个子线程来展示值的继承
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 在主线程中设置 InheritableThreadLocal 的值
        inheritableThreadLocal.set("Parent Thread Value");

        // 创建并启动一个子线程,子线程会继承主线程的 InheritableThreadLocal 值
        Thread childThread = new Thread(() -> {
            // 子线程中打印 InheritableThreadLocal 的值
            System.out.println("Child Thread InheritableThreadLocal Value: " + inheritableThreadLocal.get());
        });
        childThread.start();

        // 主线程中打印 InheritableThreadLocal 的值
        System.out.println("Main Thread InheritableThreadLocal Value: " + inheritableThreadLocal.get());
    }
}

输出示例:

Main Thread InheritableThreadLocal Value: Parent Thread Value
Child Thread InheritableThreadLocal Value: Parent Thread Value

在这个示例中,父线程设置了 InheritableThreadLocal 的值 "Parent Thread Value"。当创建并启动子线程时,子线程能够继承并访问这个值。

自定义继承行为

如果希望在子线程中修改继承的值或进行特殊处理,可以通过重写 InheritableThreadLocalchildValue(T parentValue) 方法来定制子线程的值。例如:

InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<String>() {
        @Override
        protected String childValue(String parentValue) {
            return parentValue + " - Modified in Child Thread";
        }
    };

Demo:

public class InheritableThreadLocalDemo {
    // 创建一个 InheritableThreadLocal 对象,用于在不同线程之间传递字符串值
//    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();


    private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<String>() {
        @Override
        protected String childValue(String parentValue) {
            return parentValue + " - Modified in Child Thread";
        }
    };

    /**
     * 程序的入口点
     * 在主线程中设置 InheritableThreadLocal 的值,并启动一个子线程来展示值的继承
     *
     * @param args 命令行参数
     */
    public static void main(String[] args) {
        // 在主线程中设置 InheritableThreadLocal 的值
        inheritableThreadLocal.set("Parent Thread Value");

        // 创建并启动一个子线程,子线程会继承主线程的 InheritableThreadLocal 值
        Thread childThread = new Thread(() -> {
            // 子线程中打印 InheritableThreadLocal 的值
            System.out.println("Child Thread InheritableThreadLocal Value: " + inheritableThreadLocal.get());
        });
        childThread.start();

        // 主线程中打印 InheritableThreadLocal 的值
        System.out.println("Main Thread InheritableThreadLocal Value: " + inheritableThreadLocal.get());
    }
}

输出:

Main Thread InheritableThreadLocal Value: Parent Thread Value
Child Thread InheritableThreadLocal Value: Parent Thread Value - Modified in Child Thread

注意事项

  • 继承限制:虽然子线程可以继承父线程的 InheritableThreadLocal 值,但子线程对值的修改不会影响父线程的值。

  • 内存泄漏风险:和 ThreadLocal 类似,如果不适当地使用 InheritableThreadLocal,也可能会导致内存泄漏问题,尤其是在线程池中使用时,需谨慎处理 remove() 操作。

小结

InheritableThreadLocal 扩展了 ThreadLocal 的功能,使得父线程的本地变量可以被其子线程继承。这对于需要在多个线程之间共享上下文信息的场景非常有用,如用户身份管理、事务管理等。然而,使用时需要注意避免内存泄漏和其他潜在问题。


TransmittableThreadLocal

背景

在 Java 的并发编程中,ThreadLocalInheritableThreadLocal 提供了线程本地变量的功能,但它们在一些复杂场景中存在局限性:

  • 线程池复用问题:在使用线程池时,线程被复用,导致子线程执行时可能使用的是之前其他任务的线程局部变量,InheritableThreadLocal 不能保证每次任务的隔离性。
  • 异步任务问题:在线程池或异步框架(如 CompletableFutureForkJoinPool)中,子线程可能在不同的时间段被执行,父子线程之间的上下文传递变得复杂且不可靠。

为了解决这些问题,阿里巴巴开发了 TransmittableThreadLocal(简称 TTL),它能够确保在线程池复用和异步任务场景下,线程上下文信息能够正确传递。

TTL(TransmittableThreadLocal)是阿里巴巴开源的一个增强版 ThreadLocal,专门用于解决在使用线程池、异步任务或其他线程复用场景下,InheritableThreadLocal 失效的问题。它在跨线程任务时,能够自动传递线程上下文信息,确保父线程的 ThreadLocal 变量在子线程中也能正确获取。

TransmittableThreadLocal 的特点

  1. 线程池复用安全TTL 可以正确地将父线程的 ThreadLocal 变量值传递给子线程,即使子线程是从线程池中复用的。
  2. 跨线程传递:即使线程在不同时间点执行,TTL 也能确保父线程的上下文信息被子线程正确继承。
  3. 自动清理机制:避免了 ThreadLocal 可能导致的内存泄漏问题。

使用方法

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

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

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

    public static void main(String[] args) {
        ttl.set("Parent Value");

        ExecutorService executorService = Executors.newFixedThreadPool(2);

        // 通过 TtlRunnable 包装 Runnable,以传递 ThreadLocal 变量
        Runnable task = TtlRunnable.get(() -> {
            System.out.println(Thread.currentThread().getName() + ": " + ttl.get());
        });

        executorService.submit(task);
        executorService.submit(task);

        executorService.shutdown();
    }
}

输出示例:

pool-1-thread-1: Parent Value
pool-1-thread-2: Parent Value

TransmittableThreadLocal 确保了即使线程池复用线程,每个线程仍然能够获取到父线程设置的 ThreadLocal 变量值。

适用场景

  • 分布式系统上下文传递:在微服务或分布式系统中,传递上下文(如请求追踪ID、用户身份信息)是非常常见的需求,TTL 可以确保这些上下文信息在异步和多线程场景下被正确传递。
  • 日志跟踪:可以确保在不同的线程中,日志记录的信息具有一致性,比如同一个请求的所有日志都包含相同的追踪ID。
  • 异步任务处理:在使用异步框架或多线程处理任务时,TTL 能确保子任务能够继承父任务的上下文。

总结

TransmittableThreadLocal 是阿里巴巴为了解决线程池复用、异步任务上下文传递问题而开发的工具,扩展了 ThreadLocal 的功能,使得在复杂并发场景下也能保持上下文信息的一致性。它适用于需要在父子线程间传递信息的场景,如分布式追踪、日志管理等,但使用时应注意可能的性能影响和复杂性。
在这里插入图片描述

  • 46
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小小工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值