面向面试!ThreadLocal 面试知识点总结~
The following article is from 程序员大厂面试 Author 厂哥
1. Threadlocal是什么?
ThreadLocal是JDK1.2加入的一个类,ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。
2.Threadlocal解决什么问题?
一个线程内变量共享,减少同一个线程内多个函数或组件之间一些公共变量传递的复杂度。
3. Threadlocal使用场景
ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。
比如:Android 中的Handler组件,其中又一个Looper,
每一个线程拥有自己独立的Looper Looper 又在Handler的多个方法中共享 因此,使用ThreadLocal保存Looper非常合适,Looper类中部分代码如下:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
// 准备Looper
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
// 获取Looper
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
4. ThreadLocal原理简单分析
图中左边是栈,右边是堆。线程的一些局部变量和引用使用的内存属于Stack(栈)区,而普通的对象是存储在Heap(堆)区。
线程运行时,我们定义的TheadLocal对象被初始化,存储在Heap,同时线程运行的栈区保存了指向该实例的引用,也就是图中的ThreadLocalRef。 当ThreadLocal的set/get被调用时,虚拟机会根据当前线程的引用也就是CurrentThreadRef找到其对应在堆区的实例,然后查看其对用的TheadLocalMap实例是否被创建,如果没有,则创建并初始化。 Map实例化之后,也就拿到了该ThreadLocalMap的句柄,那么就可以将当前ThreadLocal对象作为key,进行存取操作。
图中的虚线,表示key对应ThreadLocal实例的引用是个弱引用。
5. Threadlocal内存泄漏问题及结局方案
ThreadLocal为什么会发生内存泄漏?
前面提到每个Thread都有一个ThreadLocal.ThreadLocalMap
的map,该map的key为ThreadLocal实例,它为一个弱引用,我们知道弱引用有利于GC回收。当ThreadLocal的key == null
时,GC就会回收这部分空间,但是value却不一定能够被回收,因为他还与Current Thread存在一个强引用关系:
由于存在这个强引用关系,会导致value无法回收。如果这个线程对象不会销毁那么这个强引用关系则会一直存在,就会出现内存泄漏情况。所以说只要这个线程对象能够及时被GC回收,就不会出现内存泄漏。如果碰到线程池,那就更坑了。
那么要怎么避免这个问题呢?在前面提过,在ThreadLocalMap中的setEntry()
、getEntry()
,如果遇到key == null的情况,会对value设置为null。当然我们也可以显示调用ThreadLocal
的remove()
方法进行处理。
在netty中基于ThreadLocal这些缺点重新实现了一个独有的ThreadLocal:FastThreadLocal
。基于数组实现,同时线程Thread也进行了重写,在runnable()方法执行尾部调用FastThreadLocal.removeAll()将ThreadLocal中的所有变量清空,这样就解决了ThreadLocal的内存泄漏问题。
总结
ThreadLocal并不是用来解决线程间共享变量的问题,也不是协调线程同步而存在,而是为了方便每个线程处理自己的状态,相互之间独立而引入的一种机制。
每个Thread内部都存在一个ThreadLocal.ThreadLocalMap类型成员变量。该成员变量用来存储ThreadLocal中实际的变量值。
ThreadLocal并不是为线程保存对象的副本,它仅仅只起到一个索引的作用。它的主要目的是为每一个线程隔离一个类的实例,这个实例的作用范围仅限于线程内部
参考
http://www.jasongj.com/java/threadlocal/ https://juejin.im/post/6887937212780904462 https://epitomm.github.io/post/mian-shi-ti-threadlocal/ https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
---END---
更文不易,点个“在看”支持一下👇