浅谈 ThreadLocal
有时,你希望将每个线程数据(如用户ID)与线程关联起来。尽管可以使用局部变量来完成此任务,但只能在本地变量存在时才这样做。也可以使用一个实例属性来保存这些数据,但是这样就必须处理线程同步问题。幸运的是,Java提供了java.lang.ThreadLocal
类是一个简单(而且非常方便)的选择。
每个ThreadLocal
实例都描述了一个线程局部变量,它是一个变量,它为每个访问变量的线程提供一个单独的存储槽(storage slot)。你可以将线程局部变量看作是一个多槽变量,其中每个线程可以在同一个变量中存储不同的值。每个线程只看到它的值,并且不知道其他线程在这个变量中有自己的值。
一般将ThreadLocal
声明为ThreadLocal <T>
,其中T
标识存储在该变量中的值的类型。 这个类声明了下面的构造方法和方法:
ThreadLocal()
:创建一个新的线程局部变量。T get()
:返回调用线程存储槽中的值。 如果线程调用此方法时不存在条目,则get()方法调用initialValue()方法。T initialValue()
:创建调用线程的存储槽并在此槽中存储初始(默认)值。 初始值默认为null。 必须子类化ThreadLocal
并重写此受保护的方法以提供更合适的初始值。void remove()
:删除调用线程的存储槽。如果这个方法后面是get()
,而没有插入set()方法
,则get()
调用initialValue()
。
*void set(T value)
:将调用线程的存储槽的值设置为value
。
下面代码显示了如何使用ThreadLocal
将不同的用户ID与两个线程相关联的示例。
package concurrency;
public class ThreadLocalDemo {
private static volatile ThreadLocal<String> userID = new ThreadLocal<String>();
public static void main(String[] args) {
Runnable task = () -> {
String name = Thread.currentThread().getName();
if (name.equals("A")) {
userID.set("yaya");
} else {
userID.set("maomao");
}
System.out.println(name + " " + userID.get());
};
Thread threadA = new Thread(task);
threadA.setName("A");
Thread threadB = new Thread(task);
threadB.setName("B");
threadA.start();
threadB.start();
} // end method main
} // end class ThreadLocalDemo
在实例化ThreadLoca
l并将引用分配给名为userID
的volatile实例属性(该属性是volatile的,因为它是由不同的线程访问,这可能在多处理器/多核机器上执行——也可以指定为final),默认主线程创建两个线程在userID中存储不同的String对象并输出它们的值。
在Eclipse运行此程序,输出的结果为:
A yaya
B maomao
存储在线程局部变量中的值都互不相关的。 当一个新的线程被创建时,它会得到一个包含initialValue()
值的新的存储槽。 也许你想从父线程(创建另一个线程的线程)传递值到子线程(创建的线程)。 则可以使用InheritableThreadLocal
完成此任务。
InheritableThreadLocal
是ThreadLocal
的一个子类。 除了声明的InheritableThreadLocal()
构造方法外,该类声明以下受保护的方法:
T childValue(T parentValue)
:在创建子线程时,计算这个可继承线程局部变量的子线程的初始值,将该值作为父线程值的一个函数。在启动子线程之前,从父线程内部调用此方法。该方法仅返回其输入变量,如果所需要的是其他行为,则应该重写此方法。
下面代码显示了如何使用InheritableThreadLocal
将父线程的Integer对象传递给子线程。
public class InheritableThreadLocalDemo {
private static final InheritableThreadLocal<Integer> intVal = new InheritableThreadLocal<Integer>();
public static void main(String[] args) {
Runnable rP = () -> {
intVal.set(new Integer(10));
Runnable rC = () -> {
Thread thd = Thread.currentThread();
String name = thd.getName();
System.out.printf("%s %d%n", name, intVal.get());
};
Thread thdChild = new Thread(rC);
thdChild.setName("Child");
thdChild.start();
};
new Thread(rP).start();
}
}
实例化InheritableThreadLocal
并将其分配给final的实例属性(也可以使用volatile替代)intVal
后,默认主线程创建一个父线程,该线程存储包含intVal
中值的10的Integer对象。 父线程创建一个子线程,它访问intVal
并检索其父线程的Integer对象。
执行此程序,其运行结果如下:
Child 10
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
2017-04-03 Groovy 学习手册(5)