JDK源码——ThreadLocal

在这里插入图片描述

提供的方法

remove(): 移除当前线程的局部变量值。调用此方法后,当前线程将不再持有任何局部变量值。
set(T): 为当前线程设置一个新的局部变量值。参数T是要设置的值的类型。
get(): 获取当前线程的局部变量值。返回类型为T。
withInitial(Supplier): 创建一个新的ThreadLocal实例,其初始值由提供的Supplier提供。这个方法返回一个新的ThreadLocal对象,其类型为S。

应用场景

ThreadLocal主要应用于以下场景:

  1. 数据库连接管理:在多线程环境下,每个线程都需要独立的数据库连接。使用ThreadLocal可以确保每个线程都有自己的数据库连接,避免了多个线程共享同一个连接导致的问题。

  2. SimpleDateFormat线程安全问题:SimpleDateFormat类不是线程安全的,如果在多线程环境下使用同一个SimpleDateFormat实例进行日期格式化,可能会导致数据错乱。通过使用ThreadLocal为每个线程提供一个独立的SimpleDateFormat实例,可以避免这个问题。

  3. Spring事务管理:Spring框架中的事务管理器(TransactionManager)通常使用ThreadLocal来存储当前线程的事务信息。这样可以确保在多线程环境下,每个线程都能正确地处理自己的事务。

  4. 用户身份验证和会话管理:在Web应用程序中,通常需要对每个用户进行身份验证并维护用户的会话信息。使用ThreadLocal可以在每个线程中存储用户的身份信息和会话信息,使得这些信息在整个请求处理过程中都可以被访问到。

  5. 缓存管理:在多线程环境下,可以使用ThreadLocal来存储每个线程的缓存数据,避免不同线程之间的缓存数据冲突。

示例

下面是一个使用ThreadLocal的简单示例,演示了如何在多线程环境下为每个线程提供独立的数据库连接:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class DatabaseConnectionManager {
    private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();

    public static Connection getConnection() throws SQLException {
        Connection connection = connectionHolder.get();
        if (connection == null) {
            // 创建一个新的数据库连接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydatabase", "username", "password");
            connectionHolder.set(connection);
        }
        return connection;
    }

    public static void closeConnection() throws SQLException {
        Connection connection = connectionHolder.get();
        if (connection != null) {
            connection.close();
            connectionHolder.remove();
        }
    }
}

在这个示例中,我们使用了ThreadLocal来存储每个线程的数据库连接。当调用getConnection()方法时,首先检查当前线程是否已经有一个数据库连接,如果没有,则创建一个新的连接并将其存储在ThreadLocal中。这样,每个线程都可以独立地访问和关闭自己的数据库连接,避免了资源共享的问题。

原理

ThreadLocal的实现原理主要基于Java的线程局部变量。每个线程都有一个自己的线程局部变量副本,这个副本只能由当前线程访问,其他线程无法访问。

具体来说,ThreadLocal通过创建一个静态的内部类ThreadLocalMap来实现线程局部变量的存储。每个线程都会拥有一个自己的ThreadLocalMap实例,这个实例中存储了线程的局部变量。ThreadLocalMap是一个自定义的哈希映射表,它的键是ThreadLocal对象,值是线程的局部变量。

当调用ThreadLocal的set方法时,它会将值存储在当前线程的ThreadLocalMap中,以ThreadLocal对象作为键。而get方法则是从当前线程的ThreadLocalMap中获取与ThreadLocal对象关联的值。

由于每个线程都有自己独立的ThreadLocalMap,因此每个线程只能访问到自己的局部变量,从而实现了线程之间的隔离。

需要注意的是,虽然ThreadLocal可以实现线程之间的隔离,但它并不能解决多线程环境下的资源共享问题。如果多个线程共享同一个ThreadLocal实例,那么它们仍然会共享相同的值。因此,在使用ThreadLocal时,需要确保每个线程都有自己的ThreadLocal实例,以避免资源竞争和数据不一致的问题。

另外,ThreadLocal还提供了一些额外的方法,如remove和withInitial。remove方法用于从当前线程的ThreadLocalMap中移除与ThreadLocal对象关联的值,以便及时释放资源。而withInitial方法则用于创建一个新的ThreadLocal实例,并指定一个初始值。

源码阅读

属性

public class ThreadLocal<T> {
    /**
     * ThreadLocals依赖于每个线程附加的线性探测哈希映射(Thread.threadLocals和inheritableThreadLocals)。
     * ThreadLocal对象充当键,通过threadLocalHashCode进行搜索。这是一个自定义哈希码(仅在ThreadLocalMap中有用),
     * 它可以消除连续构造的ThreadLocals被同一线程使用时的冲突,同时在较少见的情况下保持良好行为。
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * 下一个要分配的哈希码。原子更新。从零开始。
     */
    private static AtomicInteger nextHashCode = new AtomicInteger();

    /**
     * 连续生成的哈希码之间的差值 - 将隐式顺序线程局部ID转换为接近最优分布的乘法哈希值,适用于2的幂大小的表。
     */
    private static final int HASH_INCREMENT = 0x61c88647;
}

set

这段代码是一个Java方法,用于设置当前线程中ThreadLocal变量的值。大多数子类不需要重写此方法,只需依赖initialValue方法来设置线程局部变量的值。

/**
 * 将当前线程中此线程局部变量的副本设置为指定值。大多数子类无需重写此方法,
 * 仅依赖{@link #initialValue}方法来设置线程局部变量的值。
 *
 * @param value 要存储在当前线程中此线程局部变量副本的值。
 */
public void set(T value) {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 获取与当前线程关联的ThreadLocalMap
    if (map != null) { // 如果ThreadLocalMap不为空
        map.set(this, value); // 在ThreadLocalMap中设置当前ThreadLocal对象的值
    } else {
        createMap(t, value); // 如果ThreadLocalMap为空,则创建一个新的ThreadLocalMap并设置值
    }
}

这个方法首先获取当前线程,然后尝试从与该线程关联的ThreadLocalMap中获取当前ThreadLocal对象的映射。如果映射存在,就在映射中设置值。如果映射不存在,就创建一个新的ThreadLocalMap并设置值。

get

/**
 * 返回当前线程中此线程局部变量的值。如果当前线程中该变量没有值,
 * 则首先将其初始化为调用{@link #initialValue}方法所返回的值。
 *
 * @return 此线程局部的当前线程值
 */
public T get() {
    Thread t = Thread.currentThread(); // 获取当前线程
    ThreadLocalMap map = getMap(t); // 获取与当前线程关联的ThreadLocalMap
    if (map != null) { // 如果ThreadLocalMap不为空
        ThreadLocalMap.Entry e = map.getEntry(this); // 从ThreadLocalMap中获取当前ThreadLocal对象的条目
        if (e != null) { // 如果条目不为空
            @SuppressWarnings("unchecked")
            T result = (T)e.value; // 获取条目中的值,并转换为泛型类型T
            return result; // 返回结果
        }
    }
    return setInitialValue(); // 如果没有找到值,则初始化并返回值
}

这个方法首先获取当前线程,然后尝试从与该线程关联的ThreadLocalMap中获取当前ThreadLocal对象的条目。如果找到了条目,就返回条目中的值。如果没有找到条目,就调用setInitialValue方法来初始化值,并返回这个初始值。

getMap和createMap

明白,以下是翻译后的内容:

/**
 * 获取与ThreadLocal关联的映射。在InheritableThreadLocal中被覆盖。
 *
 * @param  t 当前线程
 * @return 映射
 */
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

/**
 * 创建与ThreadLocal关联的映射。在InheritableThreadLocal中被覆盖。
 *
 * @param t 当前线程
 * @param firstValue 映射的初始条目的值
 */
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

getMap方法用于获取当前线程上的ThreadLocal映射。这个映射包含了与该线程绑定的ThreadLocal变量的副本。如果在InheritableThreadLocal类中有相应的实现,那么这个方法将会被重写。

createMap方法用于在当前线程上创建一个新的ThreadLocal映射,并将传入的firstValue作为这个映射的初始值。这个映射会将当前ThreadLocal对象作为键,firstValue作为值。如果在InheritableThreadLocal类中有相应的实现,那么这个方法也会被重写。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

吴代庄

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

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

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

打赏作者

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

抵扣说明:

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

余额充值