提供的方法
remove(): 移除当前线程的局部变量值。调用此方法后,当前线程将不再持有任何局部变量值。
set(T): 为当前线程设置一个新的局部变量值。参数T是要设置的值的类型。
get(): 获取当前线程的局部变量值。返回类型为T。
withInitial(Supplier): 创建一个新的ThreadLocal实例,其初始值由提供的Supplier提供。这个方法返回一个新的ThreadLocal对象,其类型为S。
应用场景
ThreadLocal主要应用于以下场景:
-
数据库连接管理:在多线程环境下,每个线程都需要独立的数据库连接。使用ThreadLocal可以确保每个线程都有自己的数据库连接,避免了多个线程共享同一个连接导致的问题。
-
SimpleDateFormat线程安全问题:SimpleDateFormat类不是线程安全的,如果在多线程环境下使用同一个SimpleDateFormat实例进行日期格式化,可能会导致数据错乱。通过使用ThreadLocal为每个线程提供一个独立的SimpleDateFormat实例,可以避免这个问题。
-
Spring事务管理:Spring框架中的事务管理器(TransactionManager)通常使用ThreadLocal来存储当前线程的事务信息。这样可以确保在多线程环境下,每个线程都能正确地处理自己的事务。
-
用户身份验证和会话管理:在Web应用程序中,通常需要对每个用户进行身份验证并维护用户的会话信息。使用ThreadLocal可以在每个线程中存储用户的身份信息和会话信息,使得这些信息在整个请求处理过程中都可以被访问到。
-
缓存管理:在多线程环境下,可以使用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
类中有相应的实现,那么这个方法也会被重写。