ThreadLocal概述
ThreadLocal 是 Java 提供的一个类,用于解决多线程中共享变量的问题。它提供了一种线程局部(thread-local)变量。这些变量不同于它们的正常变量,因为每一个访问这个变量的线程都有其自己的、独立初始化的变量副本。因此,线程局部变量不是多线程共享的,它们在线程的生命周期内起作用,是线程私有的。
ThreadLocal 是 Java 中的一个线程封闭技术,它允许每个线程都有自己的局部变量,该变量对其他线程不可见,从而解决了多线程环境下共享变量的线程安全性问题。
通过 ThreadLocal,可以在每个线程中创建一个独立的变量副本,使得每个线程都可以独立地操作自己的变量副本,而不会相互影响。这在某些情况下能够简化代码逻辑,并提高代码的并发性能。
ThreadLocal详解
ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本
ThreadLocal本身并不存储数据,它使用了线程中的threadLocals属性,threadLocals的类型就是在
ThreadLocal中的定义的ThreadLocalMap对象,当调用ThreadLocal的set(T value)方法时,ThreadLocal将自身的引用也就是this作为Key,然后,把用户传入的值作为Value存储到线程的ThreadLocalMap中,这就相当于每个线程的读写操作都是基于线程自身的一个私有副本,线程之间的数据是相互隔离的,互不影响。
ThreadLocal 不是用来解决共享对象访问的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。在每个线程内部都有对目标资源的“副本”。每个线程对自己内部的资源进行修改,不影响别的线程中该资源的状态,ThreadLocal 提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保 存有一个变量副本,每个线程的变量都不同。ThreadLocal 相当于提供了一种线程隔离,将变量与线程相绑定。
Threadlocal 适用于在多线程的情况下,可以实现传递数据,实现线程隔离。
ThreadLocal 提供给我们每个线程缓存局部变量
ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,
特别适用于各个线程依赖不通的变量值完成操作的场景。
a.简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了
b.事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。ThreadLocal 是一种线程隔离机制,它提供了多线程环境下对于共享变量访问的安全性。
在多线程访问共享变量的场景中,一般的解决办法是对共享变量加锁,从而保证在同一时刻只有一个线程能够对共享变量进行更新,并且基于 Happens-Before规则里面的监视器锁规则,又保证了数据修改后对其他线程的可见性。但是加锁会带来性能的下降,所以 ThreadLocal 用了一种空间换时间的设计思想,也就是说在每个线程里面,都有一个容器来存储共享变量的副本,然后每个线程只对自己的变量副本来做更新操作,这样既解决了线程安全问题,又避免了多线程竞争加锁的开销。
ThreadLocal 的 具 体 实 现 原 理 是 , 在 Thread 类 里 面 有 一 个 成 员 变 量ThreadLocalMap,它专门来存储当前线程的共享变量副本,后续这个线程对于共享变量的操作,都是从这个 ThreadLocalMap 里面进行变更,不会影响全局共享变量的值。
在 ThreadLocal 中,除了空间换时间的设计思想以外,还有一些比较好的设计思想,比如线性探索解决 hash 冲突,数据预清理机制、弱引用 key 设计尽可能避免内存泄漏等。
每个线程调用全局 ThreadLocal 对象的 set 方法,在 set 方法中,首先根据当前线程获取当前线程的ThreadLocalMap 对象,然后往这个 map 中插入一条记录,key 其实是 ThreadLocal 对象,value 是各自的 set方法传进去的值。也就是每个线程其实都有一份自己独享的 ThreadLocalMap对象,该对象的 Key 是 ThreadLocal对象,值是用户设置的具体值。在线程结束时可以调用 ThreadLocal.remove()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的 ThreadLocal 变量。
ThreadLocal 的作用和目的
用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据
Threadlocal 基本 API
1.New Threadlocal();—创建 Threadlocal
2.set 设置当前线程绑定的局部变量
3.get 获取当前线程绑定的局部变量
4.remove() 移除当前线程绑定的变量
ThreadLocal 有哪些使用场景
数据库连接和 session 管理
数据库连接的隔离、对于客户端请求会话的隔离
数据库连接:在数据库连接池中,可以使用 ThreadLocal 来保存每个线程自己的数据库连接,这样可以避免在多线程环境中出现连接冲突。
用户会话信息:在 web 应用中,用户的会话信息(如用户 ID、用户角色等)可以保存在 ThreadLocal 中,方便在当前线程中传递和使用。
线程安全的单例:虽然 ThreadLocal 不是用来实现单例模式的,但可以在单例模式的基础上,结合 ThreadLocal 来为每个线程提供一个线程安全的实例。
获取 HttpRequest 、Aop 调用链、JDBC 连接 Session 管理 、Spring 事务管理
线程安全的单例:虽然 ThreadLocal 不是用来实现单例模式的,但可以在单例模式的基础上,结合 ThreadLocal 来为每个线程提供一个线程安全的实例。
获取 HttpRequest 、Aop 调用链、JDBC 连接 Session 管理 、Spring 事务管理
ThreadLocal 的常见用途主要是确保每个线程都有独立的数据副本,避免多线程之间的共享冲突。在日常开发中,ThreadLocal 常用于以下几类场景:
数据库连接:为每个线程提供独立的数据库连接或事务上下文。
事务管理:每个线程维护独立的事务。
日志上下文:为每个线程维护独立的日志上下文信息(如用户 ID、请求 ID 等)。
Web Session:在 Web 环境中维护每个请求的用户会话。
缓存:每个线程有自己的缓存副本。
计算上下文:在计算过程中存储中间结果。
安全上下文:保存当前线程的认证和授权信息。
异步任务上下文:在线程池或异步任务中传递上下文信息。
ThreadLocal 在多线程编程中是一个非常实用的工具,可以帮助开发者避免线程之间的数据竞争和共享状态问题,但也需要小心其潜在的内存泄漏问题,尤其是在应用结束时要显式地清理 ThreadLocal 数据。
ThreadLocal 是 Java 提供的一个工具类,用于在多线程环境中为每个线程提供独立的变量副本。每个线程可以独立地访问和修改 ThreadLocal 中的变量,而不会影响其他线程。常见的使用场景主要集中在以下几个方面:
- 数据库连接(数据库连接池)
在数据库连接池中,ThreadLocal 常用于为每个线程提供独立的数据库连接或事务上下文。这样可以避免多个线程共享数据库连接导致的冲突,同时提高性能。
示例:
public class DatabaseConnectionManager {
private static ThreadLocal<Connection> threadLocalConnection = ThreadLocal.withInitial(() -> {
try {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/db", "user", "password");
} catch (SQLException e) {
throw new RuntimeException("Failed to create database connection", e);
}
});
public static Connection getConnection() {
return threadLocalConnection.get();
}
public static void closeConnection() {
try {
threadLocalConnection.get().close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
threadLocalConnection.remove();
}
}
}
在这个例子中,每个线程都会有一个独立的 Connection 对象,避免了线程之间的干扰。
2. 事务管理
事务通常是线程隔离的,每个线程执行的数据库操作必须在独立的事务上下文中进行。使用 ThreadLocal 可以方便地为每个线程维护一个独立的事务状态,确保事务的一致性。
示例:
public class TransactionContext {
private static ThreadLocal<Transaction> transactionThreadLocal = new ThreadLocal<>();
public static void beginTransaction() {
Transaction tx = new Transaction();
transactionThreadLocal.set(tx);
tx.start();
}
public static void commit() {
Transaction tx = transactionThreadLocal.get();
if (tx != null) {
tx.commit();
transactionThreadLocal.remove();
}
}
public static void rollback() {
Transaction tx = transactionThreadLocal.get();
if (tx != null) {
tx.rollback();
transactionThreadLocal.remove();
}
}
public static Transaction getTransaction() {
return transactionThreadLocal.get();
}
}
通过 ThreadLocal,每个线程会有自己独立的事务对象,不会互相影响。
3. 日志上下文(MDC:Mapped Diagnostic Context)
在日志框架(如 SLF4J + Logback 或 Log4j)中,ThreadLocal 被广泛用于维护线程级别的上下文信息,如用户 ID、请求 ID、事务 ID 等,以便在分布式环境中进行更精确的日志跟踪。
示例:
import org.slf4j.MDC;
public class LoggingContext {
public static void setUserId(String userId) {
MDC.put("userId", userId);
}
public static void clear() {
MDC.clear();
}
}
在多线程环境中,ThreadLocal 会为每个线程维护独立的 MDC 信息,确保在不同线程中记录的日志都包含正确的上下文信息。
4. Session管理(Web应用中)
在 Web 应用中,ThreadLocal 可以用于为每个请求提供独立的会话上下文。在一些框架中,可以通过 ThreadLocal 存储与当前请求相关的用户会话信息或用户身份信息。
示例:
public class UserSession {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
public static void setCurrentUser(User user) {
userThreadLocal.set(user);
}
public static User getCurrentUser() {
return userThreadLocal.get();
}
public static void clear() {
userThreadLocal.remove();
}
}
每个线程(对应每个请求)会持有自己的 User 对象,避免多线程间的共享。
5. 缓存机制
在一些场景下,ThreadLocal 用来缓存某些数据,以避免多次计算或重复操作。例如,计算复杂的数据或进行数据预处理时,可以将计算结果存储在 ThreadLocal 中,避免每个线程都执行相同的操作。
示例:
public class ThreadLocalCache {
private static ThreadLocal<Map<String, Object>> cacheThreadLocal = ThreadLocal.withInitial(HashMap::new);
public static void put(String key, Object value) {
cacheThreadLocal.get().put(key, value);
}
public static Object get(String key) {
return cacheThreadLocal.get().get(key);
}
public static void clear() {
cacheThreadLocal.remove();
}
}
这样每个线程都有自己的缓存,避免了多个线程间的缓存污染。
6. 计算上下文
在一些复杂的计算过程中,可能需要维护一些中间状态或者计算结果,使用 ThreadLocal 可以将这些计算状态限定在当前线程中,避免多线程竞争。
示例:
public class CalculationContext {
private static ThreadLocal<Double> calculationResult = new ThreadLocal<>();
public static void setCalculationResult(double result) {
calculationResult.set(result);
}
public static double getCalculationResult() {
return calculationResult.get();
}
public static void clear() {
calculationResult.remove();
}
}
在计算过程中,可以使用 ThreadLocal 保存线程的计算结果,避免不同线程间的相互干扰。
7. SecurityContext(安全上下文)
在一些框架(如 Spring Security)中,ThreadLocal 用于存储当前线程的安全上下文信息,如当前认证用户的信息、权限等。这是为了确保在整个线程生命周期内,安全上下文信息能够正确传递。
示例:
public class SecurityContextHolder {
private static ThreadLocal<SecurityContext> securityContextThreadLocal = new ThreadLocal<>();
public static void setContext(SecurityContext context) {
securityContextThreadLocal.set(context);
}
public static SecurityContext getContext() {
return securityContextThreadLocal.get();
}
public static void clear() {
securityContextThreadLocal.remove();
}
}
ThreadLocal 保证每个线程都有独立的 SecurityContext,避免多线程环境下的安全问题。
8. 异步任务和线程池中传递上下文
在使用线程池或者执行异步任务时,ThreadLocal 可以用来在不同的线程之间传递执行上下文。例如,传递一个用户请求的上下文或者某些需要在整个任务生命周期内保持一致的信息。
示例:
public class AsyncContext {
private static ThreadLocal<String> requestId = new ThreadLocal<>();
public static void setRequestId(String id) {
requestId.set(id);
}
public static String getRequestId() {
return requestId.get();
}
public static void clear() {
requestId.remove();
}
}
通过 ThreadLocal,可以在异步任务中保持与当前任务相关的上下文信息。
实现原理
每个 Thread 线程内部都有一个 ThreadLocalMap;以线程作为 key,泛型作为 value,可以理解为线程级别的缓存。每一个线程都会获得一个单独的 map。
提供了 set 和 get 等访问方法,这些方法为每个使用该变量的线程都存有一份独立的副 本,因此 get 方法总是返回由当前执行线程在调用 set 时设置的最新值
ThreadLocal 是一个解决线程并发问题的一个类,用于创建线程的本地变量,我们知道一个对 象的所有线程会共享它的全局变量,所以这些变量不是线程安全的,我们可以使用同步技术。 但是当我们不想使用同步的时候,我们可以选择 ThreadLocal 变量。例如,由于 JDBC 的连接对象不是线程安全的,因此,当多线程应用程序在没有协同的情况下,使用全局变量时,就不是线程安全的。通过将 JDBC 的连接对象保存到 ThreadLocal 中,每个线程都会拥有属于 自己的连接对象副本
ThreadLocal解决hash冲突
ThreadLocal使用的是自定义的ThreadLocalMap,接下来我们来探究一下ThreadLocalMap的hash冲突解决方式。
ThreadLocal通过开放地址法来解决hash冲突
HashMap则通过链地址法来解决hash冲突
ThreadLocal中解决hash冲突是链地址,如果冲突继续找下一个,不是rehash
rehash的解释:在创建hashMAP的时候可以设置来个参数
ThreadLocalMap使用闭散列:(开放地址法或者也叫线性探测法)解决哈希冲突。
threadLocal是通过开放寻址法来解决hash冲突的,就是一个新的key计算出的index位置的entry不为null时 ,它回去找下一个相邻的entry,如果为null就存储,不为null继续寻找下一个,直到找到空的把元素放进去为止,如果元素个数超过阈值,就进行resize扩容。
ThreadLocal中如果发生了hash冲突,会查到下一位,直到找到一个key为空的位置。例如,现在算出的hash值为2,但是位置2已经有一个key不为空的元素,那么会去找位置3,如果位置3可用就会直接使用,如果不可用,就继续查找位置4。以此类推
Threadlocal 为何引发内存泄漏问题
内存泄漏 :表示就是我们程序员申请了内存,但是该内存一直无法释放;
如果不会被使用的对象或者变量占用的内存不能被回收,就是内存泄漏。如果泄漏的数据量足够大,可能会引起内存溢出,导致程序异常结束。
因为每个线程中都有自己独立的 ThreadLocalMap 对象,key 为 ThreadLocal,value 是为 变量值。 Key 为 ThreadLocal 作为 Entry 对象的 key,是弱引用,当 ThreadLocal 指向 null 的时候, Entry 对象中的 key 变为 null,GC 如果没有清理垃圾时,则该对象会一直无法被垃圾收集机制回收,一直占用到了系统内存,有可能会发生内存泄漏的问题
每个线程都有一个自己的ThreadLocalMap,是线程独有的,别的线程无法访问到你所在的线程的ThreadLocalMap,ThreadLocalMap是entry结构的,他的key(是Threadlocal对象)是弱引用, 当不存在外部强引用的时候,在垃圾回收的时候会随时被回收,但是他的value是强引用,强引用链是Thread->ThreadLocalMap->entry->value。可以知道,只有当Thread被回收的时候,这个value才有机会被垃圾回收,否则,只要线程不退出,value就没有被回收的机会。这是导致ThreadLocal内存泄漏的原因。解决办法是在ThreadLocalMap进行set(),get(),remove()的时候都会自己清理,最好的做法还是不用了就remove()掉。
如何防御 Threadlocal 内存泄漏问题
1.每次使用完ThreadLocal都调用它的remove()方法清除数据。
2.将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉
由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。
但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用
强引用与弱引用
强引用: 如 String name = new String(); 一个对象具有强引用,不会被垃圾回收器回收。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不回收这种对象。可将 name = null,让JVM在合适的时候回收。
弱引用: JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示。可以在缓存中使用弱引用。
GC是怎样判断对象能否被回收
引用计数: 对象被引用的时候,计数器加1,当计数器为0的时候代表对象可以被回收。
可达性分析: 从GC Roots向下搜索,也就是从根对象往下搜索,经过的地方为引用链,如果对象不在引用链,则代表可被回收。
基本用法
创建 ThreadLocal 实例:
ThreadLocal<String> threadLocal = new ThreadLocal<>();
设置值:在线程中设置值,这个值只会对该线程可见。
threadLocal.set(“thread value”);
获取值:在线程中获取值,如果该线程之前没有设置过值,则返回 null 或通过 ThreadLocal 的 initialValue() 方法返回初始值(如果提供了的话)。
String value = threadLocal.get();
移除值:在线程中移除值。这不会影响到其他线程中的值。
threadLocal.remove();
初始值:ThreadLocal 提供了一个 initialValue() 方法,可以在没有设置值的情况下提供一个默认值。不过,这个方法通常是在 get() 方法被首次调用,并且之前没有调用 set(T value) 方法时才被调用。
ThreadLocal<String> threadLocalWithDefault = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "default value";
}
};
● 使用 ThreadLocal 时要注意内存泄漏的问题。由于 ThreadLocal 的生命周期通常与线程的生命周期相同,如果线程长时间运行并且 ThreadLocal 引用了大对象,那么这些对象可能不会被垃圾收集器回收,从而导致内存泄漏。因此,在不再需要 ThreadLocal 时,应该调用 remove() 方法来清理它。
● 在使用 ThreadLocal 传递数据时,要确保数据的线程安全性。虽然 ThreadLocal 本身提供了线程隔离,但如果传递的数据是可变的,并且在线程之间共享,那么仍然可能出现线程安全问题。
2386

被折叠的 条评论
为什么被折叠?



