说一下ThreadLocal
1.ThreadLocal 是java中所提供的线程本地存储机制,可以利用该机制将数据(如对象)缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据
2.ThreadLocal底层是通过ThreadLocalMap实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
3.如果线程池中使用ThreadLocal会造成内存泄漏,因为当使用完ThreadLocal对象后,理应当把设置的key、value,也就是Entry对象进行回收,但是线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏。解决方法,在一个线程使用完ThreadLocal后,手动调用ThreadLocal对象的remove方法移除Entry对象。
4.ThreadLocal经典应用场景是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享一个连接)
一.ThreadLocal使用案例
//定义一个Person类,其中的成原变量使用了ThreadLocal
public class Person {
ThreadLocal<String> name=new ThreadLocal<>();
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);;
}
public Person() {
}
}
//测试方法,创建了两个线程,每个线程分别等待3秒,并对Person中的成员变量进行赋值
public class Test {
public static void main(String[] args) {
Person person = new Person();
new Thread(()->{
try{
Thread.sleep(3000);
}catch (Exception e){
System.out.println(e);
}
person.setName("一号");
System.out.println("1线程:"+person.getName());
}).start();
new Thread(()->{
try{
Thread.sleep(3000);
}catch (Exception e){
System.out.println(e);
}
person.setName("二号");
System.out.println("2线程:"+person.getName());
}).start();
}
}
100次测试结果都为:
1线程:一号
2线程:二号
主要原因是就是ThreadLocal:ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,防止自己的变量被其它线程篡改。
底层其实就是当我们在一个公共类Person中声明了一个ThreadLocal对象后,当一个线程使用该person并使用set进行赋值时,会将 <ThreadLocal,对应的数据> 封装成一个Entry对象,并将其放入每个线程独有的ThreadThreadMap中。
我们看看在Thread类中这个Map是哪个成员变量:
结构如下:
当一个线程创建时,就会初始化这个threadLocals,当我们调用set方法时,底层代码如下;
//class ThreadLocal
public void set(T value) {
Thread t = Thread.currentThread(); //当前线程
ThreadLocalMap map = getMap(t); //找到当前线程对应的threadLocals
if (map != null)
map.set(this, value); // map在线程开始时就已经创建好了,我们这里直接将ThreadLocal和value装入map中
else
createMap(t, value);
}
调用get方法时,源码如下:
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
从上面可知,每个线程执行的get和set都是执行线程本地缓存的数据,从而使线程之间非共享数据得到了隔离。
二.内存泄漏问题
内存泄漏:不再使用的对象没有被垃圾回收,导致jvm的内存压力逐渐增大
当一个线程结束后,线程相关的对象如ThreadLocalMap中的Entry会被垃圾回收。但是在线程池中,一个线程不会被销毁,他会做不同的逻辑任务,Entry对象不会被垃圾回收,但是之前的Entry对象我们不会再使用,这样就耗费了jvm的内存,即内存泄漏问题。解决方法,在公共对象中提供remove方法
public void remove(){
this.name.remove();
}
这样在一个线程使用完一个ThreadLocal属性后,手动调用remove方法,将本线程中ThreadLocalMap中对应ThreadLocal的key进行删除即可,这样就避免了内存泄漏问题。