重点:ThreadLocal问题总结
1. ThreadLocal是什么?(作用)
是实现线程局部变量的工具类。让每个访问该变量的线程都拥有自己的独立副本,实现线程隔离。
2. ThreadLocal的实现原理
ThreadLocal 是通过每个线程内部持有一个私有的 ThreadLocalMap (ThreadLocal 类的一个静态内部类)来实现的。
当我们调用 set 方法时,实际上是获取当前线程对象,并把数据存入这个线程自带的 Map 中,Key 就是当前的 ThreadLocal 对象。Value可以是任意类型。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。
3. ThreadLocal的优点
1️⃣ 每个线程访问的变量副本均独立,避免了共享变量引起的线程安全问题。
2️⃣ 实现了变量的线程独占,使变量不需要同步处理,避免资源竞争。
3️⃣ 可用于跨方法、跨类时传递上下文数据,不需要在方法间传递参数。
4. ThreadLocal的问题
问题:内存泄露。ThreadLocalMap 的 Key 是 弱引用,但 Value 是强引用。如果一个线程一直在运行,并且 value 一直指向某个强引用对象,那么这个对象就不会被回收,从而导致内存泄漏。
解决方案:使用完 ThreadLocal 后,及时调用 remove() 方法释放内存空间。
补充:
ThreadLocalMap 内部维护了一个 Entry 类型的数组。每一个 Entry 都是一个键值对:
Key:指向 ThreadLocal 对象的弱引用(WeakReference)。
Value:具体要存储的变量副本(强引用)
什么是弱引用,什么是强引用?
强引用:User user = new User("沉默王二") 中,user 就是一个强引用, new User("沉默王二") 就是强引用对象。当 user 被置为 null 时( user = null ), new User("沉默王二") 对象就会被垃圾回收;否则即便是内存空间不足,JVM 也不会回收 new User("沉默王二") 这个强引用对象,宁愿抛出 OutOfMemoryError。
弱引用:调用 set 方法后,会将 key = new ThreadLocal<>() 放入 ThreadLocalMap 中,此时的 key 是一个弱引用对象。当 JVM 进行垃圾回收时,如果发现了弱引用对象,就会将其回收。
当ThreadLocal与线程池结合使用时,可能会导致内存泄漏的问题。
这是因为线程池中的线程在执行完任务后,并不会被销毁,而是重新放入线程池中以供重用。如果在任务执行过程中使用了ThreadLocal,并且没有手动清除其中的数据,那么这些数据会一直保留在线程中。由于线程池中的线程是可重用的,当线程被复用时,原来线程中遗留的ThreadLocal数据依然存在,如果没有及时清理,这些数据会一直占用内存,并且对应的ThreadLocal实例也不会被回收。随着线程池的不断使用,内存中积累的无用ThreadLocal实例和数据也会越来越多,从而导致内存泄漏。
(为什么线程池会存在复用问题?因为 ThreadLocal 的生命周期是和线程绑定的。在线程池中,线程在执行完任务后不会销毁,如果前一个任务没有调用 remove() 清理数据,那么下一个任务在复用该线程时,就会读取到上个任务残留的旧数据,造成‘数据污染’。)
为了避免这个问题,使用ThreadLocal时需要特别注意在使用完毕后及时清理数据。可以通过在任务执行完毕后手动调用ThreadLocal的remove()方法(在finally中)来清除对应线程的ThreadLocal数据。另外,还可以使用线程池的钩子函数,在线程池中的线程执行完任务后自动清理ThreadLocal数据。
##牛客AI配图神器#
是实现线程局部变量的工具类。让每个访问该变量的线程都拥有自己的独立副本,实现线程隔离。
2. ThreadLocal的实现原理
ThreadLocal 是通过每个线程内部持有一个私有的 ThreadLocalMap (ThreadLocal 类的一个静态内部类)来实现的。
当我们调用 set 方法时,实际上是获取当前线程对象,并把数据存入这个线程自带的 Map 中,Key 就是当前的 ThreadLocal 对象。Value可以是任意类型。当调用ThreadLocal的get()方法的时候,会先找到当前线程的ThreadLocalMap,然后再找到对应的值。
3. ThreadLocal的优点
1️⃣ 每个线程访问的变量副本均独立,避免了共享变量引起的线程安全问题。
2️⃣ 实现了变量的线程独占,使变量不需要同步处理,避免资源竞争。
3️⃣ 可用于跨方法、跨类时传递上下文数据,不需要在方法间传递参数。
4. ThreadLocal的问题
问题:内存泄露。ThreadLocalMap 的 Key 是 弱引用,但 Value 是强引用。如果一个线程一直在运行,并且 value 一直指向某个强引用对象,那么这个对象就不会被回收,从而导致内存泄漏。
解决方案:使用完 ThreadLocal 后,及时调用 remove() 方法释放内存空间。
补充:
ThreadLocalMap 内部维护了一个 Entry 类型的数组。每一个 Entry 都是一个键值对:
Key:指向 ThreadLocal 对象的弱引用(WeakReference)。
Value:具体要存储的变量副本(强引用)
什么是弱引用,什么是强引用?
强引用:User user = new User("沉默王二") 中,user 就是一个强引用, new User("沉默王二") 就是强引用对象。当 user 被置为 null 时( user = null ), new User("沉默王二") 对象就会被垃圾回收;否则即便是内存空间不足,JVM 也不会回收 new User("沉默王二") 这个强引用对象,宁愿抛出 OutOfMemoryError。
弱引用:调用 set 方法后,会将 key = new ThreadLocal<>() 放入 ThreadLocalMap 中,此时的 key 是一个弱引用对象。当 JVM 进行垃圾回收时,如果发现了弱引用对象,就会将其回收。
当ThreadLocal与线程池结合使用时,可能会导致内存泄漏的问题。
这是因为线程池中的线程在执行完任务后,并不会被销毁,而是重新放入线程池中以供重用。如果在任务执行过程中使用了ThreadLocal,并且没有手动清除其中的数据,那么这些数据会一直保留在线程中。由于线程池中的线程是可重用的,当线程被复用时,原来线程中遗留的ThreadLocal数据依然存在,如果没有及时清理,这些数据会一直占用内存,并且对应的ThreadLocal实例也不会被回收。随着线程池的不断使用,内存中积累的无用ThreadLocal实例和数据也会越来越多,从而导致内存泄漏。
(为什么线程池会存在复用问题?因为 ThreadLocal 的生命周期是和线程绑定的。在线程池中,线程在执行完任务后不会销毁,如果前一个任务没有调用 remove() 清理数据,那么下一个任务在复用该线程时,就会读取到上个任务残留的旧数据,造成‘数据污染’。)
为了避免这个问题,使用ThreadLocal时需要特别注意在使用完毕后及时清理数据。可以通过在任务执行完毕后手动调用ThreadLocal的remove()方法(在finally中)来清除对应线程的ThreadLocal数据。另外,还可以使用线程池的钩子函数,在线程池中的线程执行完任务后自动清理ThreadLocal数据。
##牛客AI配图神器#
全部评论
相关推荐
