一、什么是ThreadLocal:
从字面上解释,ThreadLocal即为线程本地变量,意思是ThreadLocal中的变量属于当前线程,该变量对其他线程是隔离的。
在多线程时,为了保证线程安全,通常会使用加锁的方式对共享变量进行访问,但除了这种方式之外,还可以用ThreadLocal,通过线程所使用到的变量对其他线程隔离来保证线程安全。
二、ThreadLocal的使用:
public class ThreadLocalTest {
// new一个threadLocal对象tl
static ThreadLocal<String> tl = new ThreadLocal<>();
public static void main(String[] args) {
// 在线程thread1中,往tl中放入"hello",并打印tl中的对象
new Thread(() -> {
tl.set("hello");
System.out.println(Thread.currentThread().getName() + tl.get());
}, "thread1").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在线程thread2中,尝试取出tl中的对象并打印
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + tl.get());
}, "thread2").start();
}
}
输出:
thread1hello
thread2null
可以看到,在threadlocal中,thread1放入的对象thread2并不能访问到。
三、ThreadLocal实现原理:
1、从set方法看起
首先会拿到当前线程,调用getMap方法拿到一个当前线程的ThreadLocalMap,如果map不为空,threadlocal作为key,将值set到map中,否则创建一个ThreadLocalMap并将值set进去。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
2、getMap(Thread)
从getMap中可以看到,返回的是线程自己本身的成员变量threadLocals,即set中所用到的map是当前线程自己的对象。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
四、ThreadLocal结构:
在Thread中维护着一个threadLocalMap对象,它里面的key为threadLocal,value为threadLocal中set的值。
五、ThreadLocalMap如何解决hash冲突:
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
}
ThreadLocalMap中维护着一个Entry数组,用于储存key、value,ThreadLocalMap是通过线性探测法去找到Entry的特定位置进行存储的。
线性探测法:首先会对threadLocal对象的hash进行一些计算,然后对数组长度取模,找到数组的某个位置进行存储,如果该位置已经被占据,则index+1继续寻找,直到找到空闲位置为止,若index+1>length-1,则将index置为0。
六、如何避免内存泄露:
内存泄露:有些对象不会被使用,但永远不会被JVM回收。
1、如何避免ThreadLocalMap中key的内存泄露:
由于ThreadLocal对象被线程里的ThreadLocalMap中的key指向,就算不在其它地方使用到此ThreadLocal对象,它也永远不会被回收。真是这样吗?
通过源码可以发现,Entry继承于一个弱引用对象,它的key是被一个弱引用指向的。
首先解释什么是弱引用:每次GC时,如果发现一个对象是只由弱引用指向的对象,会被立即回收。
所以当线程的其它地方不再使用到此ThreadLocal对象后,每次GC时,就会把此ThreadLocal对象回收,不会造成内存泄露。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
2、如何避免ThreadLocalMap中的value的内存泄露
由于指向value的引用为强引用,所以就算其它地方不再使用到value时(key被回收),它也可能不会自动被回收调,所以需要主动调用threadLocal.remove方法主动将ThreadLocalMap中对应的value置为null,GC时才会回收value对象。
注:
由于ThreadLocal是通过线性探测法解决hash冲突,在get使用线性探测法的过程中,会找到key为null的entry,顺便会将它们的value置为null。
ThreadLocal在扩容时,会将不再使用到的value置为null。
七、ThreadLocal应用场景:
有些线程内共享变量,可以考虑保存在ThreadLocal中,避免每次都需要增加方法参数进行传递。例如:
不同的线程使用不同的Session时。
当不同的线程使用不同的数据库连接时。