从字面意思来看非常容易理解,但是从实际使用的角度来看,就没那么容易了,作为一个面试常问的点,使用场景那也是相当的丰富:
1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、Spring关于事务的处理,用于存储线程事务信息。
4、Mybatis关于分页的处理
关于initialValue()方法,是由子类重写的,当get时。没有set的时候就去获取init方法。否则获取set的值
public class Profiler { // 第一次get()方法调用时会进行初始化(如果set方法没有调用) //每个线程会调用一次 private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>() { //初始化重写了返回1,第一次get()方法调用时,没有set就会返回 protected Long initialValue() { return 1L; } }; public static final void begin() { //TIME_THREADLOCAL.set(10L); } public static final long end() { return TIME_THREADLOCAL.get(); } public static void main(String[] args) throws Exception { Profiler.begin(); TimeUnit.SECONDS.sleep(1); System.out.println("get取到的值:" + Profiler.end() + " "); } }当begin方法里没有set时,get方法取到的是初始化的值。 当begin方法里有set方法时,get方法取到的是set的值
我们看下这个例子
public class ThreadLocal_learn { public static void main(String[] args) { ThreadLocal<Person> threadLocal=new ThreadLocal<>(); Person person=new Person("张三"); new Thread(()->{ threadLocal.set(person); System.out.println("线程"+Thread.currentThread().getName()+":"+threadLocal.get().name); }).start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //线程2 new Thread(()->{ System.out.println("线程"+Thread.currentThread().getName()+":"+threadLocal.get()); }).start(); } } class Person{ String name; public Person(String name) { this.name = name; } }定义个全局的对象person,在第一个线程里set,在第二个线程里get,那么我们能拿到吗,看运行结果 可以看见线程2是拿不到线程1里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); }获取当前线程,调用getMap方法
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }t是当前线程,threadLocals 是当前前程的成员变量ThreadLocalMap 也就是每个线程都有这个map,键就是ThreadLocal,值就是传进来的值。
然后看map.set(this, value); 会发现是new了一个Entry,这就是键值对。 进入这个 会发现这个Entry继承了个弱引用,调用了弱引用的构造方法,key就是ThreadLocal对象,值就是传进去的值。 因为ThreadLocalMap是属于线程本身的所以,不同的线程是只能拿本线程的变量。所以在上面的例子里线程2是拿不到线程1的对象
那么我们看下为什么 这里用到了弱引用? 还是看这个图 但是即使这样依然还会有内存泄漏的存在。
ThreadLocal对象被回收,key的值就是null,则会导致整个value再也无法被访问到,因此依然存在内存泄漏。所以需要把这个Entry给remove掉 并且在线程池的时候,用完一定要手动调用remove。因为如果不清理,用的就是原来旧的值。
