从实例和源码角度简析 ThreadLocal

本文从实例和源码角度详细解析ThreadLocal,包括其是什么、如何使用、源码解析以及适用场景。通过源码分析,揭示ThreadLocal如何在线程间创建独立的数据存储,确保线程局部变量的隔离性。
摘要由CSDN通过智能技术生成

注:此文源码摘自 sun jdk 1.8

ThreadLocal 是什么

打开 ThreadLocal 的源码我们可以看到如下的注释:

这里写图片描述

大致翻译如下:

该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

例如,以下类生成对每个线程唯一的局部标识符。 线程 ID 是在第一次调用 UniqueThreadIdGenerator.getCurrentThreadId() 时分配的,在后续调用中不会更改。

  import java.util.concurrent.atomic.AtomicInteger;

 public class UniqueThreadIdGenerator {

     private static final AtomicInteger uniqueId = new AtomicInteger(0);

     private static final ThreadLocal < Integer > uniqueNum = 
         new ThreadLocal < Integer > () {
             @Override protected Integer initialValue() {
                 return uniqueId.getAndIncrement();
         }
     };

     public static int getCurrentThreadId() {
         return uniqueId.get();
     }
 } // UniqueThreadIdGenerator

每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

简单而言,ThreadLocal 是一个线程自身内部的数据存储类,其它线程是无法访问该线程的数据的。这样说起来还是挺抽象的,我们下面来介绍一个例子。

ThreadLocal 的使用

代码如下:

public class Test {
    private static final ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return "default";
        }
    };

    public static void main(String[] args) {
        threadLocal.set("thread#0");
        System.out.println("thread#0 " + threadLocal.get());

        new Thread("thread#1") {
            @Override
            public void run() {
                System.out.println(currentThread().getName() + " " + threadLocal.get());
            }
        }.start();

        new Thread("thread#2") {
            @Override
            public void run() {
                threadLocal.set("thread#2");
                System.out.println(currentThread().getName() + " " + threadLocal.get());
            }
        }.start();
    }
}

我们首先创建一个 ThreadLocal<String> 对象,并重写它的 initialValue() 方法,这样它默认情况下就是返回一个字符串为 default 的字段。然后我们再 main() 方法里面创建两个线程,线程 thread#1thread#2,当然,加上它本身,一共是三个线程,我们分别在主线程调用 set("thread#0")thread#1 线程不调用 set() 方法;thread#2 调用 set("thread#2") 方法。我们发现,输出结果如下:

这里写图片描述

对于主线程,由于我们调用了 set("thread#0") 方法,所以 threadLocal.get() 的值就是字符串 thread#0;对于 thread#1 线程没有调用 set() 方法,故 threadLocal.get() 返回的就是默认值 default 字符串;对于 thread#2 线程,我们调用了 set("thread#2") 方法,故 threadLoca.get() 返回的就是字符串 thread#2

所以我们可以发现,即使是同一个 ThreadLocal 对象,我们的 set()get() 方法都是仅对当前线程可见的,各个线程之间不可见不可相互影响。

ThreadLocal 源码解析

要想搞清楚 ThreadLocal 为什么会有这样的特性,我们其实只需要搞清楚我们上面所使用到的 set()get() 方法即可。set() 方法源码如下:

这里写图片描述

第一行代码不解释了。我们看到第二行代码可以获取到一个 ThreadLocalMap,我们不妨戳进 getMap() 方法里面看一下,ThreadLocalMap 是如何和 Thread 参数关联在一起的,源码如下:

这里写图片描述

我们再戳进 Thread 类看一下 ——

这里写图片描述

这下清楚了,先获取当前的线程对象,再获取这个对象中的 ThreadLocalMap 对象,并且调用它的 set() 方法将当前的 ThreadLocal 对象和传入的 value 值存入。那么 ThreadLocalMap 又是个什么呢?它的 set() 方法肯定是个关键点,又是怎样的呢?我们再来看看 ——

这里写图片描述

ThreadLocalMap 其实就是 ThreadLocal 的一个内部类

这里写图片描述

这里写图片描述

到这里就一目了然了,ThreadLocalMap 中维护了一个 Entry[]Entry 是一个泛型类,其构造函数需要传入两个参数,一个是 ThreadLocal 对象,作为 Reference,另一个是 Object 对象,作为 value),然后我们就只需要将当前的 ThreadLocal 对象和传入的值放入这个数组的某个位置就可以了。

接下来我们再来看看 get() 方法,其源码如下:

这里写图片描述

这个源码很简单了,就是获取到当前线程对象的所持有的 ThreadLocalMap 对象,传入当前的 ThreadLocal 对象由此获得到对应的 ThreadLocalMap.Entry 对象,再取出它的 value 即可。

到此所有的流程已经走了一遍了,我们再来理一遍思路:首先创建一个全局共享的 ThreadLocal 对象,因为 ThreadLocal 不应该是依赖某一个具体的线程的,然后假设我们使用了三个线程 A、B、C 来对该 ThreadLocal 对象进行操作,线程 A 先调用 ThreadLocal 对象的 set() 方法,那么线程 A 中的 ThreadLocalMap 里面的 Entry 数组就会在某个位置保存这个 ThreadLocal 对象和 set() 进来的值;然后线程 B 调用 ThreadLocal 对象的 set() 方法,那么线程 B 中的 ThreadLocalMap 里面的 Entry 数组就会在某个位置保存这个 ThreadLocal 对象和 set() 进来的值;然后线程 C 调用 ThreadLocal 对象的 set() 方法,那么线程 C 中的 ThreadLocalMap 里面的 Entry 数组就会在某个位置保存这个 ThreadLocal 对象和 set() 进来的值。每个线程所持有的 ThreadLocalMap 对象是不同的,故其 ThreadLocalMap 里面的 Entry 数组也是不同的,故它们之间的 set()get() 方法是互不相见的。

ThreadLocal 使用场景

在 Android 中,ThreadLocal 比较知名的一个场景就是 Looper 的使用,我们可以在 Lopper 类的源码中找到 ThreadLocal 的影子,因为一个线程对应一个 Looper,这时使用 ThreadLocal 就再好不过了。那么为什么要在 Looper 中使用 ThreadLocal,而不能是别的什么替代方案呢?假如我们不使用 ThreadLocal,那么我们可以使用一个全局的哈希表来维护线程和 Looper 的关系,这样显然不如 ThreadLocal 来的方便。一般来说,当某些数据是以线程为作用域并且不同线程之间有不同的数据副本的话,就可以采用 ThreadLocal

扩展链接:理解Java中的ThreadLocal

深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
深度学习是机器学习的一个子领域,它基于人工神经网络的研究,特别是利用多层次的神经网络来进行学习和模式识别。深度学习模型能够学习数据的高层次特征,这些特征对于图像和语音识别、自然语言处理、医学图像分析等应用至关重要。以下是深度学习的一些关键概念和组成部分: 1. **神经网络(Neural Networks)**:深度学习的基础是人工神经网络,它是由多个层组成的网络结构,包括输入层、隐藏层和输出层。每个层由多个神经元组成,神经元之间通过权重连接。 2. **前馈神经网络(Feedforward Neural Networks)**:这是最常见的神经网络类型,信息从输入层流向隐藏层,最终到达输出层。 3. **卷积神经网络(Convolutional Neural Networks, CNNs)**:这种网络特别适合处理具有网格结构的数据,如图像。它们使用卷积层来提取图像的特征。 4. **循环神经网络(Recurrent Neural Networks, RNNs)**:这种网络能够处理序列数据,如时间序列或自然语言,因为它们具有记忆功能,能够捕捉数据中的时间依赖性。 5. **长短期记忆网络(Long Short-Term Memory, LSTM)**:LSTM 是一种特殊的 RNN,它能够学习长期依赖关系,非常适合复杂的序列预测任务。 6. **生成对抗网络(Generative Adversarial Networks, GANs)**:由两个网络组成,一个生成器和一个判别器,它们相互竞争,生成器生成数据,判别器评估数据的真实性。 7. **深度学习框架**:如 TensorFlow、Keras、PyTorch 等,这些框架提供了构建、训练和部署深度学习模型的工具和库。 8. **激活函数(Activation Functions)**:如 ReLU、Sigmoid、Tanh 等,它们在神经网络中用于添加非线性,使得网络能够学习复杂的函数。 9. **损失函数(Loss Functions)**:用于评估模型的预测与真实值之间的差异,常见的损失函数包括均方误差(MSE)、交叉熵(Cross-Entropy)等。 10. **优化算法(Optimization Algorithms)**:如梯度下降(Gradient Descent)、随机梯度下降(SGD)、Adam 等,用于更新网络权重,以最小化损失函数。 11. **正则化(Regularization)**:技术如 Dropout、L1/L2 正则化等,用于防止模型过拟合。 12. **迁移学习(Transfer Learning)**:利用在一个任务上训练好的模型来提高另一个相关任务的性能。 深度学习在许多领域都取得了显著的成就,但它也面临着一些挑战,如对大量数据的依赖、模型的解释性差、计算资源消耗大等。研究人员正在不断探索新的方法来解决这些问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值