ThreadLocal介绍
ThreadLocal是JAVA中用于解决线程安全问题的一种机制,它允许创建线程局部变量,即每个线程都有自己独立的变量副本,从而避免了线程间的资源共享和同步问题。
从内存结构图,我们可以看到
- Thread类中,有个ThreadLocal.ThreadLocalMap的成员变量。
- ThreadLocalMap内部维护了Entry数组,每个Entry代表一个完整的对象,key是ThreadLocal本身,value是ThreadLocal的泛型对象值。
ThreadLocal的原理
ThreadLocal的实现依赖于Thread类中的一个ThreadLocalMap字段,这是一个存储ThreadLocal变量本身和对应值的映射。
每个线程都有自己的ThreadLocalMap实例,用于存储该线程所持有的所有ThreadLocal变量的值。
当你创建一个ThreadLocal变量的时候,它实际上就是一个ThreadLocal对象的实例,每个ThreadLocal对象都可以存储任意类型的值,这个值对于每个线程来说是独立的。
get()、set()和remove()方法
get()方法
当调用ThreadLocal的get()方法的时候,ThreadLocal会检查当前线程的ThreadLocalMap中是否有与值关联的值。
如果有,就返回该值
如果没有,会调用initialValue()方法初始化该值,然后将其放入ThreadLocalMap中并返回。
如果之前没有设置过值,ThreadLocal会调用initialValue()方法来获取一个默认值。
举个例子:
ThreadLocal<String> threadLocal = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "default value";
}
};
String value = threadLocal.get(); // 如果没有设置过值,将返回"default value"
如果当前线程的ThreadLocalMap中没有与该ThreadLocal对象关联的值,get()方法会调用ThreadLocal的initialValue()方法来获取一个默认值,并将这个值存储在ThreadLocalMap中。
如果ThreadLocalMap中已经存在与该ThreadLocal对象关联的值,get()方法会直接返回这个值,而不会调用initialValue()方法。
set()方法
当调用set()方法的时候,ThreadLocal会将给定值与当前线程管来弄起来
也就是在ThreadLocalMap中存储一个键值对,key是ThreadLocal对象本身,value是传入的值。
remove()方法
当调用remove()方法的时候,ThreadLocal会从当前线程的ThreadLocalMap中删除与该ThreadLocal对象关联的值。
可能存在的问题
ThreadLocal对象内部有一个ThreadLocalMap类型的成员变量,这个Map的生命周期和线程本身是绑定的,只要线程不销毁,这个Map就会一直存在。
ThredLocalMap存储数据用的Entry继承了WeakReference,它的结构是这样的:
key是对ThreadLocal对象的弱引用,而value是我们存入的值,它是强引用。
内存泄漏问题
当一个ThreadLocal对象在外部没有了强引用(比如方法执行结束),下一次GC发生的时候,由于Entry的key是弱引用,所以ThreadLocal对象会被回收。
这时,ThreadLocalMap中就会出现key为Null的Entry,这个Entry的value仍然是强引用的,这就导致了内存泄漏。只要线程不结束,ThreadLocalMap就会一直持有这个值,无法被回收,Entry会一直持有这个value的强引用,导致value无法被垃圾回收。如果线程是从线程池中复用的,那么这个线程的生命周期会很长,这些value就会极少称多,最终导致内存泄漏。
因此,为了防止ThreadLocal内存泄漏,在使用完ThreadLocal之后,必须在代码中调用ThreadLocal的remove()方法来清除当前线程中的ThreadLocal变量。
ThreadLocal作用
线程隔离: ThreadLocal为每个线程提供了独立的变量副本,意味着线程之间不会相互影响,可以安全地在多线程环境中使用这些变量,不需要担心数据竞争或者同步问题。
降低耦合度: 在同一个线程内的多个函数或者组件之间,使用ThreadLocal可以减少参数的传递,降低代码之间的耦合度,使得代码更加清晰和模块化。
性能优势: 由于ThreadLocal避免了线程之间的同步开销,所以在大量线程并发执行的时候,相比传统的锁机制,可以提供更好的性能。