ThreadLocal详解

[复制链接]
发表于 2026-2-24 11:35:32 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
ThreadLocal

ThreadLocal文档表明:
  1. This class provides thread-local variables.
  2. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own,
  3. independently initialized copy of the variable.
复制代码
文档大意:这个类提供线程局部变量。这些变量与平常变量的差别之处在于,每个访问它们的线程(通过其get方法或set方法)都有本身的独立初始化的变量副本。
如文档表明所说,ThraedLocal为每个利用该变量的线程提供独立的变量副本,以是每一个线程都可以独立地改变本身的副本,而不会影响别的线程所对应的副本。每个访问ThreadLocal变量的线程都有本身的隔离副本,如许防止了线程之间的干扰,消除了同步的须要。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。说白了ThreadLocal就是存放线程的局部变量的。
对比线程同步

ThreadLocal是修饰变量的,重点是在控制变量的作用域,初志不是为了办理线程并发和线程辩论的,而是为了让变量的种类变的更多更丰富,方便利用。许多开辟语言在语言级别都提供这种作用域的变量范例。
着实要包管线程安全,并不肯定就是要举行同步,两者没有因果关系。同步只是包管共享数据竞争时的本事。如果一个方法原来就不涉及共享数据,那它天然就无需任何同步步调去包管精确性。线程安全,并不肯定就是要举行同步,ThreadLocal目标是线程安全,但不是同步本事。
ThreadLocal和线程同步机制都可以办理多线程中共享变量的访问辩论题目。在同步机制中,通过对象的锁机制包管同一时间只有一个线程访问变量。利用同步机制要求步伐审慎地分析什么时间对变量举行读写,什么时间须要锁定某个对象,什么时间开释对象锁等繁杂的题目,步伐筹划和编写难度相对较大。而ThreadLocal 则从另一个角度来办理多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问辩论。由于每一个线程都拥有本身的变量副本,从而也就没有须要对该变量举行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
固然ThreadLocal可以或许包管多线程访问数据安全,但是由于在每个线程中都创建了副本,以是要思量它对资源的斲丧,比如内存的占用会比倒霉用ThreadLocal要大。对于多线程资源共享的题目,同步机制接纳了“以时间换空间”的方式,而ThreadLocal接纳了“以空间换时间”的方式。前者仅提供一份变量,让差别的线程列队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
利用示例

在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。API方法也相应举行了调解,新版本的API方法分别是void set(T value)、T get()。
ThreadLocal中紧张有三个方法:


  • set():设置当火线程的线程局部变量的值。
  • get():该方法返回当火线程所对应的线程局部变量。
  • remove():删除当火线程的线程局部变量,目标是为了镌汰内存的占用。
  1. public class ThreadLocalExample {
  2.     // 创建一个 ThreadLocal 变量,用于存储每个线程独立的值
  3.     private static final ThreadLocal<String> threadLocalValue = new ThreadLocal<>();
  4.     public static void main(String[] args) {
  5.         Runnable task1 = () -> {
  6.             // 设置线程局部变量的值
  7.             threadLocalValue.set("Thread-1's Value");
  8.             // 获取并打印线程局部变量的值
  9.             System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
  10.             // 删除线程局部变量的值
  11.             threadLocalValue.remove();
  12.             System.out.println(Thread.currentThread().getName() + " after remove: " + threadLocalValue.get());
  13.         };
  14.         Runnable task2 = () -> {
  15.             // 设置线程局部变量的值
  16.             threadLocalValue.set("Thread-2's Value");
  17.             // 获取并打印线程局部变量的值
  18.             System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
  19.             // 删除线程局部变量的值
  20.             threadLocalValue.remove();
  21.             System.out.println(Thread.currentThread().getName() + " after remove: " + threadLocalValue.get());
  22.         };
  23.         Thread thread1 = new Thread(task1);
  24.         Thread thread2 = new Thread(task2);
  25.         thread1.start();
  26.         thread2.start();
  27.     }
  28. }
复制代码
除此之外,ThreadLocal提供了一个withInitial()方法同一初始化全部线程的ThreadLocal的值。
  1. public class ThreadLocalWithInitialExample {
  2.     // 使用 withInitial 方法提供初始值
  3.     private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
  4.     public static void main(String[] args) {
  5.         Runnable task1 = () -> {
  6.             // 获取并打印线程局部变量的值
  7.             SimpleDateFormat df = dateFormat.get();
  8.             String formattedDate = df.format(new Date());
  9.             System.out.println(Thread.currentThread().getName() + ": " + formattedDate);
  10.             // 删除线程局部变量的值
  11.             dateFormat.remove();
  12.         };
  13.         Runnable task2 = () -> {
  14.             // 获取并打印线程局部变量的值
  15.             SimpleDateFormat df = dateFormat.get();
  16.             String formattedDate = df.format(new Date());
  17.             System.out.println(Thread.currentThread().getName() + ": " + formattedDate);
  18.             // 删除线程局部变量的值
  19.             dateFormat.remove();
  20.         };
  21.         Thread thread1 = new Thread(task1);
  22.         Thread thread2 = new Thread(task2);
  23.         thread1.start();
  24.         thread2.start();
  25.     }
  26. }
复制代码
ThreadLocal是一种强盛的工具,实用于须要线程隔离的场景,如用户会话、数据库毗连和格式化对象等。利用ThreadLocal可以有用地管理线程本地的数据,克制多线程环境下的竞争和数据划一性题目。但是由于ThreadLocal的生命周期与线程相干,如果在线程池中利用ThreadLocal,须要留意及时调用remove()方法清算线程局部变量,来防止内存走漏。
实现原理

ThreadLocal类本身并不存储线程本地变量的值,而是通过ThreadLocalMap来实现。每个线程内部都有一个ThreadLocalMap实例,ThreadLocal变量作为ThreadLocalMap的键,存储的值是该线程对应的变量值。
set方法起首获取当火线程 Thread 对象,然后获取该线程的 ThreadLocalMap 实例。如果存在,则将值存储在 ThreadLocalMap 中;否则,创建一个新的 ThreadLocalMap。
  1. public void set(T value) {
  2.     // 获取当前线程
  3.     Thread t = Thread.currentThread();
  4.     // 每个线程 都有一个自己的ThreadLocalMap
  5.     // ThreadLocalMap 里就保存着所有的ThreadLocal变量
  6.     ThreadLocalMap map = getMap(t);
  7.     if (map != null)
  8.         // 向map里添加值
  9.         map.set(this, value);
  10.     else
  11.         // map为null,创建一个 ThreadLocalMap
  12.         createMap(t, value);
  13. }
  14. // 全局定义的localMap
  15. ThreadLocal.ThreadLocalMap threadLocals = null;
  16. // 获取当前线程所持有的localMap
  17. ThreadLocalMap getMap(Thread t) {
  18.     return t.threadLocals;
  19. }
  20. // 创建,初始化 localMap
  21. void createMap(Thread t, T firstValue) {
  22.     t.threadLocals = new ThreadLocalMap(this, firstValue);
  23. }
复制代码
get方法同样先获取当火线程 Thread 对象,然后获取该线程的 ThreadLocalMap 实例。再通过 ThreadLocal 对象作为键从 ThreadLocalMap 中获取值。如果键不存在,则调用 setInitialValue 方法初始化变量。
  1. public T get() {
  2.     // 获取当前线程
  3.     Thread t = Thread.currentThread();
  4.     // 每个线程 都有一个自己的ThreadLocalMap,
  5.     // ThreadLocalMap里就保存着所有的ThreadLocal变量
  6.     ThreadLocalMap map = getMap(t);
  7.     if (map != null) {
  8.         //ThreadLocalMap的key就是当前ThreadLocal对象实例,
  9.         //多个ThreadLocal变量都是放在这个map中的
  10.         ThreadLocalMap.Entry e = map.getEntry(this);
  11.         if (e != null) {
  12.             @SuppressWarnings("unchecked")
  13.             //从map里取出来的值就是我们需要的这个ThreadLocal变量
  14.             T result = (T)e.value;
  15.             return result;
  16.         }
  17.     }
  18.     // 如果map没有初始化,那么在这里初始化一下
  19.     return setInitialValue();
  20. }
  21. // 全局定义的localMap
  22. ThreadLocal.ThreadLocalMap threadLocals = null;
  23. // 获取当前线程所持有的localMap
  24. ThreadLocalMap getMap(Thread t) {
  25.     return t.threadLocals;
  26. }
复制代码
setInitialValue 方法通过 initialValue 方法获取初始值,并存储在 ThreadLocalMap 中。如果 initialValue 方法未被重写,默认返回 null。
  1. private T setInitialValue() {
  2.     T value = initialValue();
  3.     Thread t = Thread.currentThread();
  4.     ThreadLocalMap map = getMap(t);
  5.     if (map != null)
  6.         map.set(this, value);
  7.     else
  8.         createMap(t, value);
  9.     return value;
  10. }
  11. protected T initialValue() {
  12.     return null;
  13. }
复制代码
ThreadLocalMap是一个自界说的哈希表,此中每个元素是一个Entry对象。ThreadLocalMap是一个比力特别的Map,它的每个Entry的key都是一个弱引用。
  1. static class Entry extends WeakReference<ThreadLocal<?>> {
  2.     /** The value associated with this ThreadLocal. */
  3.     Object value;
  4.     //key就是一个弱引用
  5.     Entry(ThreadLocal<?> k, Object v) {
  6.         super(k);
  7.         value = v;
  8.     }
  9. }
复制代码
如许筹划的长处是,如果这个变量不再被其他对象利用时,可以主动接纳这个ThreadLocal对象,克制大概的内存走漏。
内存走漏题目

固然ThreadLocalMap中的key是弱引用,当不存在外部强引用的时间,就会主动被接纳。但是Entry中的value依然是强引用,value的引用链条如下:
  1. Thread --> ThreadLocalMap --> Entry --> value
复制代码
只有当Thread被接纳时,这个value才有被接纳的时机,否则只要线程不退出,value总是会存在一个强引用。但是要求每个Thread都会退出,是一个极其苛刻的要求,对于线程池来说,大部门线程会不绝存在在体系的整个生命周期内,那样的话就会造成value对象出现走漏的大概。
如果get()方法总是访问固定几个不绝存在的ThreadLocal,那么清算动作就不会实验,如果你没有时机调用set()和remove(),那么这个内存走漏依然会发生。以是当你不须要这个ThreadLocal变量时,主动调用remove(),如许是可以或许克制内存走漏的。可以将ThreadLocal的利用和清算放在try-finally块中,确保remove()方法总是会被调用。
  1. ThreadLocal<MyClass> threadLocal = new ThreadLocal<>();
  2. try {
  3.     threadLocal.set(new MyClass());
  4.     // 使用线程局部变量
  5. } finally {
  6.     threadLocal.remove();
  7. }
复制代码
除此之外,应只管克制将ThreadLocal对象声明为静态变量,特别是在应用服务器或雷同环境中,由于它们的生命周期通常较长,会增长内存走漏的风险。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表