ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例
ThreadLocal简介
ThreadLocal是一个线程内部的数据存储类,它可以为每个线程提供独立的变量副本,不同线程间的变量无法相互访问和修改。这避免了每个线程都要维护一套独立变量的麻烦,并且也减少了线程之间不必要的数据争用。ThreadLocal适用于这样的场景:每个线程需要有自己单独的实例,而不是共享实例。例如,在 web 应用中,每个请求被一个新的线程处理,每个线程需要有自己的变量实例。
ThreadLocal使用示例
public class ThreadLocalExample {
// 线程局部变量,每个线程有自己的变量副本
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void set(String value) {
threadLocal.set(value);
}
public String get() {
return threadLocal.get();
}
}
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocalExample example = new ThreadLocalExample();
// 线程1设置threadLocal变量
example.set("Thread1 local variable");
System.out.println("Thread1 get: " + example.get());
// 线程2无法获取线程1设置的threadLocal变量
Thread thread2 = new Thread() {
public void run() {
example.set("Thread2 local variable");
System.out.println("Thread2 get: " + example.get());
}
};
thread2.start();
}
}
运行结果:
Thread1 get: Thread1 local variable
Thread2 get: Thread2 local variable每个线程获取自己设置的值,并不同线程间互不干扰。
ThreadLocal原理解析
ThreadLocal内部使用ThreadLocalMap来存储每个线程的变量副本。ThreadLocalMap是ThreadLocal的静态内部类,每个线程都有自己的ThreadLocalMap副本。
ThreadLocal中get()方法的实现如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
- 获取当前线程对象
- 获取当前线程的ThreadLocalMap(实际上是从当前线程的ThreadLocalMap变量中获取)
- 在ThreadLocalMap中获取当前ThreadLocal变量对应的value值
- 如果不存在,调用setInitialValue()方法初始化value值,并存储到ThreadLocalMap中
这样,每个线程的ThreadLocal变量都被存储在自己的ThreadLocalMap中,相互独立,互不干扰。
ThreadLocalMap使用ThreadLocal对象作为key来存储value值。当ThreadLocal对象被回收时,由弱引用产生的key会在下一次GC时被清除,这会导致value值无法被访问到,出现内存泄漏,所以我们应该手动调用remove()方法,在ThreadLocal不再使用时清除它。
Spring中ThreadLocal的应用
Spring框架中大量使用了ThreadLocal,例如:
- TransactionSynchronizationManager: 管理线程事务上下文信息。
- RequestContextHolder: 存储request上下文,用于获取request信息。
- LocaleContextHolder: 存储locale上下文,用于获取locale信息。
这些类都使用ThreadLocal来为每个线程提供单独变量副本,避免了线程间数据交叉和覆盖的问题。
@Component
public class RequestHolder {
private ThreadLocal<HttpServletRequest> requestHolder = new ThreadLocal<>();
public void setRequest(HttpServletRequest request) {
requestHolder.set(request);
}
public HttpServletRequest getRequest() {
return requestHolder.get();
}
}
这样每个线程在处理request时可以调用setRequest()方法存储自己的request对象,在其他地方需要获取request信息时,调用getRequest()方法即可获取当前线程对应的request对象。这就避免了每个线程都要维护一个request对象的麻烦,也减少了线程之间request对象混淆的问题。
小结
ThreadLocal为每个线程提供独立的变量副本,实现了线程隔离。它的主要作用是为每个线程保存一些 thread-local 的上下文信息,这些信息在线程的生命周期内起作用。
它的内部原理是使用ThreadLocalMap来存储每个线程对应的变量副本,键值为ThreadLocal对象,值则为变量副本。
它应用在许多地方,如Spring框架等,用于避免线程间数据交叉和覆盖的问题。
但是它也有一定的弊端,由于ThreadLocalMap使用ThreadLocal作为key,如果ThreadLocal被回收,就可能出现内存泄漏的问题。所以应该手动调用ThreadLocal的remove()方法,在ThreadLocal不再使用时清除它。
ThreadLocal的使用步骤
- 定义ThreadLocal变量:
private ThreadLocal<String> threadLocal = new ThreadLocal<>();
- 在每个线程内设置ThreadLocal变量:
threadLocal.set("value");
- 获取ThreadLocal变量:
String value = threadLocal.get();
- 删除ThreadLocal变量:
threadLocal.remove();
- 目前ThreadLocal类提供的方法有:
- set(T value): 设置当前线程的thread local变量的值。
- get(): 获取当前线程的thread local变量的值。
- remove(): 删除当前线程的thread local变量的值。
- initialValue(): 返回当前线程第一次调用get()时的值,后续调用get()会直接返回这个值。
- ThreadLocal应用举例:
- 解决数据库连接共享问题:每个线程都有自己的数据库连接,避免线程之间的连接混用。
private ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();
public Connection getConnection() {
Connection conn = connectionHolder.get();
if (conn == null) {
conn = DriverManager.getConnection("jdbc:mysql://localhost/test", "root", "root");
connectionHolder.set(conn);
}
return conn;
}
- 解决Session共享问题:每个线程都有自己的Session实例。
private ThreadLocal<HttpSession> sessionHolder = new ThreadLocal<>();
public HttpSession getSession() {
HttpSession session = sessionHolder.get();
if (session == null) {
session = request.getSession();
sessionHolder.set(session);
}
return session;
}
- ThreadLocal内存泄漏问题的解决:
由于ThreadLocalMap使用ThreadLocal作为key来存储entry,如果ThreadLocal被回收,key变成null,就会出现内存泄漏。所以ThreadLocal使用完毕后,需要调用remove()方法清除数据,避免出现内存泄漏。
threadLocal.remove();
常见面试题
- ThreadLocal能否解决线程安全问题?
答:ThreadLocal能解决线程安全问题。ThreadLocal为每个线程提供独立的变量副本,实现线程隔离。
- ThreadLocal会引起内存泄漏么?如何避免?
答:ThreadLocal会引起内存泄漏。因为ThreadLocalMap使用ThreadLocal作为key来存储entry,如果ThreadLocal被回收,key变成null,就会出现内存泄漏。
解决方法是在ThreadLocal不再使用时,手动调用remove()方法清除数据,避免出现内存泄漏。
- ThreadLocal的value为什么推荐使用引用类型?
答:因为每个线程访问自己的副本变量,如果使用基本类型,ThreadLocal需要为每个线程创建一个变量副本,这会消耗较多内存。
而如果使用引用类型,每个线程访问的都是同一个引用对象的副本,只是每个线程可以对这个对象进行修改,这可以节省内存,所以推荐ThreadLocal的value使用引用类型。
- ThreadLocalMap的工作原理是什么?
答:ThreadLocalMap是ThreadLocal的静态内部类,每个线程都有自己的ThreadLocalMap副本。
ThreadLocalMap使用ThreadLocal对象作为key来存储value值。在调用ThreadLocal的get()方法时,会先得到当前线程的ThreadLocalMap,然后再从其中获取与当前ThreadLocal对象关联的值。
put方法会将ThreadLocal对象作为key放入map中,并关联一个value。
当ThreadLocal对象被回收时,由弱引用产生的key会在下一次GC时被清除,这会导致value值无法被访问到,出现内存泄漏,所以在ThreadLocal不再使用时需要手动调用remove()方法清除数据。
- 个人理解ThreadLocal的主要作用和应用场景?
答:ThreadLocal的主要作用是为每个线程提供独立的变量副本,实现线程隔离。
它的应用场景主要有:
- 为每个线程绑定请求相关数据,避免同一个请求被不同线程处理时出现数据混淆的问题。
- 为每个线程单独绑定数据库连接、Session等资源,避免线程间共享资源。
- 解决变量共享导致的线程安全问题,通过给每个线程独立变量副本来隔离线程。
案例解析(框架源码经典案例)
这里我们以Spring中的ThreadLocal应用举个例子加深理解。
Spring中TransactionSynchronizationManager使用ThreadLocal来管理事务上下文信息。它定义了两个ThreadLocal变量:
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
- resources: 用于存储事务相关资源,如数据库连接、Session等。
- synchronizations: 用于存储事务同步对象,如事务完成后需要执行的回调等。
当开始一个事务时,通过TransactionSynchronizationManager进行事务上下文的存储:
StaticTransactionSynchronizationAdapter.registerSynchronization(
new TransactionSynchronizationAdapter() {
@Override
public void afterCompletion(int status) {
if (status == STATUS_COMMITTED) {
afterCommit();
}
else {
afterRollback();
}
}
}
);
TransactionSynchronizationManager.bindResource(ds, conn);
- 向synchronizations ThreadLocal集合中添加一个事务同步对象,用于在事务完成后执行回调。
- 向resources ThreadLocal集合中添加连接资源,以方便事务管理器管理。
事务完成时,TransactionSynchronizationManager会执行:
TransactionSynchronizationManager.initSynchronization();
try {
// 执行回调
TransactionSynchronizationManager.cleanupSynchronization();
}
finally {
// 清除ThreadLocal上的变量
TransactionSynchronizationManager.clear();
}
- 调用initSynchronization执行注册的同步回调。
- 调用cleanupSynchronization执行后续清理工作。
- 调用clear()方法清除ThreadLocal上的事务上下文,避免内存泄漏。
这样,通过ThreadLocal为每个事务线程独立存储事务上下文,避免了线程间数据混淆和干扰的问题。同时也在事务完成后手动调用clear()方法清除ThreadLocal,解决了内存泄漏的问题。
这就是ThreadLocal在Spring事务管理中的典型应用, hope这能加深您对ThreadLocal用法的理解。
案例实战
这里我们来实现一个简单的Session管理,使用ThreadLocal为每个线程单独绑定Session实例。
public class SessionManager {
private ThreadLocal<HttpSession> sessionHolder = new ThreadLocal<>();
public HttpSession getSession() {
HttpSession session = sessionHolder.get();
if (session == null) {
session = request.getSession();
sessionHolder.set(session);
}
return session;
}
public void clear() {
sessionHolder.remove();
}
}
- 定义ThreadLocal变量sessionHolder来存储每个线程的HttpSession实例。
- getSession()方法先从sessionHolder中获取 SESSION,如果不存在则创建一个新的SESSION,并存储到sessionHolder中。
- clear()方法用于手动清除sessionHolder,避免内存泄漏。
使用方式:文章来源:https://www.toymoban.com/news/detail-431909.html
// 获取Session
SessionManager manager = new SessionManager();
HttpSession session = manager.getSession();
// 使用Session
session.setAttribute("key", "value");
// 获取Attribute
String value = (String) session.getAttribute("key");
// 手动清除
manager.clear();
使用ThreadLocal为每个线程单独存储SESSION,避免了线程间SESSION实例的混淆,也能很好地管理SESSION生命周期。
同时也演示了如何防止ThreadLocal内存泄漏的问题,手动调用clear()方法清除ThreadLocal变量。此案例结合理论介绍了ThreadLocal的整个使用过程,包括定义ThreadLocal变量,为每个线程单独设置变量值,获取变量值,清除ThreadLocal变量等步骤。并分析了其工作原理和应用场景,希望能够帮助大家进一步理解和熟练掌握ThreadLocal。文章来源地址https://www.toymoban.com/news/detail-431909.html
到了这里,关于ThreadLocal的使用介绍和底层原理解析和开源框架的使用实例的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!