线程-ThreadLocal篇

发布时间 2023-11-24 23:28:31作者: 轻寒

ThreadLocal类(用于创建一个线程本地变量)

在Thread中有一个成员变量ThreadLocals,该变量的类型是ThreadLocalMap,也就是一个Map,它的键是threadLocal,值就是变量的副本。通过ThreadLocal的get()方法可以获取该线程变量的本地副本,在get方法之前要先set,否则就要重写initialValue()方法。

ThreadLocal的使用场景:

数据库连接:

1.在多线程中,如果使用懒汉式的单例模式创建Connection对象,由于该对象是共享的,那么必须要使用同步方法保证线程安全,这样当一个线程在连接数据库时,那么另外一个线程只能等待。这样就造成性能降低。如果改为哪里要连接数据库就来进行连接,那么就会频繁的对数据库进行连接,性能还是不高。这时使ThreadLocal就可以既可以保证线程安全又可以让性能不会太低。但是ThreadLocal的缺点时占用了较多的空间。
2.ThreadLocal不是一个线程而是一个线程的本地化对象。当工作于多线程环境中的对象采用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的副本。每个线程都可以独立的改变自己的副本,而不影响其他线程的副本。

内存泄露问题

概念:ThreadLocal被回收了,ThreadLocalMap Entry的key没有了指向,但Entry仍然有ThreadRef->Thread->ThreadLoalMap-> Entry value-> Object 这条引用一直存在导致内存泄露。

概率比较低,原因:

首先ThreadLocal被两种引用指向

1.ThreadLocalRef->ThreadLocal(强引用)

2.ThreadLocalMap Entry key ->ThreadLocal(弱引用)

只要ThreadLocal没被回收(使用时强引用不置null),那ThreadLocalMap Entry key的指向就不会在GC时断开被回收,也没有内存泄露一说法;通过ThreadLocal了解实现后,又知道ThreadLocalMap是依附在Thread上的,只要Thread销毁,那ThreadLocalMap也会销毁;所以非线程池环境下,也不会有长期性的内存泄露问题;而ThreadLocal实现下还做了些”保护“措施,如果在操作ThreadLocal时,发现key为null,会将其清除掉;所以,如果在线程池(线程复用)环境下,如果还会调用ThreadLocal的set/get/remove方法,发现key为null会进行清除,不会有长期性的内存泄露问题。

总结:那存在长期性内存泄露需要满足条件:ThreadLocal被回收&&线程被复用&&线程复用后不再调用ThreadLocal的set/get/remove方法,所以概率是非常低的。

为什么要将ThreadLocalMap的key设置为弱引用:

外界是通过ThreadLocal来对ThreadLocalMap进行操作的,假设外界使用ThreadLocal的对象被置null了,ThreadLocalMap的强引用指向ThreadLocal也毫无意义。弱引用反而可以预防大多数内存泄漏的情况。毕竟被回收后,下一次调用set/get/remove时ThreadLocal内部会清除掉。

为什么建议把ThreadLocal修饰为static

ThreadLocal能实现了线程的数据隔离,不在于它自己本身,而在于Thread的ThreadLocalMap。所以,ThreadLocal可以只初始化一次,只分配一块存储空间就足以了,没必要作为成员变量多次被初始化。