说一下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经典应用场景是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享一个连接)
//定义一个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进行删除即可,这样就避免了内存泄漏问题。