ThreadLocal 原理、使用、注意
1.ThreadLocal 简介1.1 ThreadLocal 的四个方法
2.ThreadLocal的核心机制/原理3.使用3.1 使用注意事项3.2 举例使用
4.面试题4.1 为什么ThreadLocalMap的key是弱引用呢?4.2 ThreadLocal为什么会内存泄漏4.3 引用相关4.4 ThreadLocal为何不用HashMap或者ConcurrentHashMap来实现而是使用ThreadLocalMap?
1.ThreadLocal 简介
一种解决多线程环境下成员变量的问题的方案,但是与线程同步无关,其思路是为每一个线程创建一个单独的变量副本,从而每个线程都可以独立地改变所拥有的变量副本,而不会影响其他线程所对应的副本。 ThreadLocal不是用于解决共享变量的问题的,也不是为了协调线程同步而存在,而是为了方便每个线程处理自己的状态而引入的一个机制。
ThreadLocal并不是一个Thread,而是Thread的局部变量。
ThreadLocal是解决线程安全问题一个很好的思路,
它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
1.1 ThreadLocal 的四个方法
简述:
1.Object get():获取该线程局部变量的值。
2.void set(Object value):给该线程局部变量赋值。
3.protected Object initialValue():返回该线程局部变量的初始值,该方法是一个protected的方法,
显然是为了让子类覆盖而设计的。
4.public void remove():将当前线程局部变量的值删除。
1.void set(Object value) 设置当前线程的线程局部变量的值。 2.public Object get() 该方法返回当前线程所对应的线程局部变量。 3.public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。 4.protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
2.ThreadLocal的核心机制/原理
ThreadLocal有一个静态内部类ThreadLocalMap,ThreadLocalMap又包含了一个Entry数组,Entry本身是一个弱引用,他的key是指向ThreadLocal的弱引用,Entry具备了保存key value键值对的能力。 弱引用的目的是为了防止内存泄露,如果是强引用那么ThreadLocal对象除非线程结束否则始终无法被回收,弱引用则会在下一次GC的时候被回收。
点击进入set(),里面用到了createMap(),再次点击,实例化了ThreadLocalMap对象,再次点击,终于到了底层(初步的底层)创建了一个table数组,这里才是真正存储数据的位置;
3.使用
3.1 使用注意事项
阿里巴巴java开发手册(嵩山版)
3.2 举例使用
public class ThreadLocalTest2 {
static ThreadLocal
public static void main(String[] args) {
new Thread(()->{
String name = Thread.currentThread().getName();
threadLocal.set("test_1");
printThread(name);
System.out.println(name + " after remove :"+threadLocal.get());
},"t1").start();
new Thread(()->{
String name = Thread.currentThread().getName();
threadLocal.set("test_2");
printThread(name);
System.out.println(name + " after remove :"+threadLocal.get());
},"t2").start();
}
static void printThread(String str){
//打印当前线程本地变量的值
System.out.println(str + " :"+threadLocal.get());
//清除t2线程本地变量的值
if ("t2".equals(Thread.currentThread().getName())){
threadLocal.remove();
}
}
}
结果:
4.面试题
4.1 为什么ThreadLocalMap的key是弱引用呢?
key 为弱引用的主要原因是为了避免内存泄漏 弱引用的目的是为了防止内存泄露(见本节4.1),不过这样还是会存在内存泄露的问题(见4.2),但是只要ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出现这个问题的。
内存泄漏问题: 如果 ThreadLocalMap 的 key 是强引用,当线程结束时,由于线程对象还持有对 ThreadLocal 实例的强引用,ThreadLocal 对象无法被正确回收,导致 ThreadLocalMap 中 的entry 无法及时被清理,从而造成内存泄漏;
弱引用解决方案: 将 ThreadLocalMap中 的 key 设为 弱引用可以有效避免这个问题,因为 弱引用 的对象在下一次垃圾回收时就会被回收,这样依赖,及时线程结束后,ThreadLcoal 对象也能够被正确的回收,从而避免内存泄漏;
弱引用的目的是为了防止内存泄露,如果是强引用那么ThreadLocal对象除非线程结束否则始终无 法被回收,弱引用则会在下一次GC的时候被回收。
但是这样还是会存在内存泄露的问题,假如key和ThreadLocal对象被回收之后,entry中就存在key 为null,但是value有值的entry对象,但是永远没办法被访问到,同样除非线程结束运行。
但是只要ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出 现这个问题的。
4.2 ThreadLocal为什么会内存泄漏
简述: ThreadLocalMap的key是弱引用,而Value是强引用。 这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生 内存泄露。
详细说明: ThreadLocal在保存的时候会把自己当做Key存在ThreadLocalMap中,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了。 这就导致了⼀个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程⼀直持续运行,那么这个Entry对象中的value就有可能⼀直得不到回收,发⽣内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以,ThreadLocal设定的value值被持有,导致内存泄露。
解决方案:ThreadLocal对象一直存在时一定要remove() ThreadLocal使用恰当,在使用完之后调用remove方法删除Entry对象,实际上是不会出现这个问题的。
4.3 引用相关
强引用(Strong Reference):在程序中正常使用的对象引用都是强引用。==如果一个对象被一个强引用所引用,那么这个对象就不会被回收,即使内存空间不足时也不会被回收。==当强引用指向的对象不再被使用,或者被赋值为 null 时,垃圾回收器才会回收该对象。
软引用(Soft Reference):软引用用于描述还有用但并非必需的对象。软引用关联的对象只有在内存不足时才会被回收。当垃圾回收器开始回收内存时,软引用所引用的对象会被保留直至内存确实不足时才会被回收。
弱引用(Weak Reference):弱引用用于描述非必需对象,它比软引用更加弱化。==被弱引用关联的对象只有在垃圾回收器运行时才会被回收。==垃圾回收器在处理弱引用时,不论内存是否充足,都会回收它所引用的对象。
虚引用(Phantom Reference):虚引用也称为幽灵引用,它是最弱的一种引用类型。虚引用关联的对象完全没有被引用,只有当其所关联的对象被垃圾回收器回收时才会被加入到引用队列中。虚引用的作用是在对象被销毁之前给出一个通知,可以在对象被销毁后进行一些清理操作。 ————————————————
强引用:User user = new User(); 这种方式就是强引用
弱引用:WeakReference weak = new WeakReference(user);这种就是弱引用了
4.4 ThreadLocal为何不用HashMap或者ConcurrentHashMap来实现而是使用ThreadLocalMap?
设计理念。ThreadLocal旨在为每个线程提供一个变量的本地副本,以避免线程间的数据共享和并发问题,而HashMap是一个用于在多个线程之间共享数据的通用数据结构。线程安全。ThreadLocal通过内部维护一个单独的变量副本来解决并发问题,而HashMap则需要额外的同步措施来确保线程安全。内部实现。ThreadLocalMap是ThreadLocal的内部数据结构,它使用弱引用来管理线程本地变量,这有助于在无其他强引用指向时回收不再使用的线程本地变量,而HashMap使用强引用来维护键值对,直到它们被显式地从Map中移除。哈希冲突处理。ThreadLocalMap使用线性探测来解决哈希冲突,这种方式相比HashMap的链地址法,在内存使用和查询效率上有所不同,ThreadLocalMap在大多数情况下能够更有效地处理哈希冲突,而HashMap则需要更复杂的处理机制,如链表或红黑树。
参考文章: https://blog.csdn.net/fd2025/article/details/120019239 https://blog.csdn.net/TM007_/article/details/134910658 https://blog.csdn.net/TM007_/article/details/134910658