ThreadLocal 源码解释:java
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. <tt>ThreadLocal</tt> instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID).
大概的理解是:ThreadLocal类用来提供线程内部的本地变量。这些变量在每一个线程内会有一个独立的初始化的副本,和普通的副本不一样,每一个线程只能访问本身的副本(经过get或set方法访问)。在一个类里边ThreadLocal成员变量一般由private static修饰。web
简单地说,ThreadLocal的做用就是为每个线程提供了一个独立的变量副本,每个线程均可以独立地改变本身的副本,而不会影响其它线程所对应的副本。api
咱们必需要区分ThreadLocal和Syncronized这种同步机制,二者面向的问题领域是不同的。sysnchronized是一种互斥同步机制,是为了保证在多线程环境下对于共享资源的正确访问。而ThreadLocal从本质上讲,无非是提供了一个“线程级”的变量做用域,它是一种线程封闭(每一个线程独享变量)技术,更直白点讲,ThreadLocal能够理解为将对象的做用范围限制在一个线程上下文中,使得变量的做用域为“线程级”。数组
没有ThreadLocal的时候,一个线程在其声明周期内,可能穿过多个层级,多个方法,若是有个对象须要在此线程周期内屡次调用,且是跨层级的(线程内共享),一般的作法是经过参数进行传递;而ThreadLocal将变量绑定在线程上,在一个线程周期内,不管“你身处何地”,只需经过其提供的get方法就可轻松获取到对象。极大地提升了对于“线程级变量”的访问便利性。tomcat
在JDK1.5之后,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。
ThreadLocal<T>类提供了4个可用的方法(基于JDK1.7版本):多线程
能够经过上述的几个方法实现ThreadLocal中变量的访问,数据设置,初始化以及删除局部变量。less
注意,在JDK1.8版本中还多了以下的这个方法:ide
/** * Creates a thread local variable. The initial value of the variable is * determined by invoking the {@code get} method on the {@code Supplier}. * * @param <S> the type of the thread local's value * @param supplier the supplier to be used to determine the initial value * @return a new thread local variable * @throws NullPointerException if the specified supplier is null * @since 1.8 */ public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) { return new SuppliedThreadLocal<>(supplier); }
ThreadLocal内部是如何为每个线程维护变量副本的呢?
在ThreadLocal类中有一个静态内部类ThreadLocalMap(概念上相似于Map),用键值对的形式存储每个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每一个线程可能存在多个ThreadLocal。测试
/** * Returns the value in the current thread's copy of this * thread-local variable. If the variable has no value for the * current thread, it is first initialized to the value returned * by an invocation of the {@link #initialValue} method. * * @return the current thread's value of this thread-local */ public T get() { Thread t = Thread.currentThread(); //当前线程 ThreadLocalMap map = getMap(t); //获取当前线程对应的ThreadLocalMap if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //获取对应ThreadLocal的变量值 if (e != null) return (T)e.value; } return setInitialValue(); //若当前线程还未建立ThreadLocalMap,则返回调用此方法并在其中调用createMap方法进行建立并返回初始值。 } // 是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } // java.lang.Thread类下, 实际上就是一个ThreadLocalMap ThreadLocal.ThreadLocalMap threadLocals = null; /** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; } /** * Sets the current thread's copy of this thread-local variable * to the specified value. Most subclasses will have no need to * override this method, relying solely on the {@link #initialValue} * method to set the values of thread-locals. * * @param value the value to be stored in the current thread's copy of * this thread-local. */ public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } /** * Create the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @param firstValue value for the initial entry of the map * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } /** * Removes the current thread's value for this thread-local * variable. If this thread-local variable is subsequently * {@linkplain #get read} by the current thread, its value will be * reinitialized by invoking its {@link #initialValue} method, * unless its value is {@linkplain #set set} by the current thread * in the interim. This may result in multiple invocations of the * <tt>initialValue</tt> method in the current thread. * * @since 1.5 */ public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类ThreadLocalMap进行操做,下面看一下该类的源代码:this
static class ThreadLocalMap { //map中的每一个节点Entry,其键key是ThreadLocal而且仍是弱引用,这也致使了后续会产生内存泄漏问题的缘由。 static class Entry extends WeakReference<ThreadLocal<?>> { Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } /** * 初始化容量为16,觉得对其扩充也必须是2的指数 */ private static final int INITIAL_CAPACITY = 16; /** * 真正用于存储线程的每一个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。 */ private Entry[] table; ///....其余的方法和操做都和map的相似 }
这个问题很容易解释,由于一个线程中能够有多个ThreadLocal对象,因此ThreadLocalMap中能够有多个键值对,存储多个value值,而若是使用线程id做为key,那就只有一个键值对了。
首先要理解内存泄露(memory leak)和内存溢出(out of memory)的区别。内存溢出是由于在内存中建立了大量在引用的对象,致使后续再申请内存时没有足够的内存空间供其使用。内存泄露是指程序申请完内存后,没法释放已申请的内存空间,(再也不使用的对象或者变量仍占内存空间)。
根据上面Entry方法的源码,咱们知道ThreadLocalMap是使用ThreadLocal的弱引用做为Key的。下图是本文介绍到的一些对象之间的引用关系图,实线表示强引用,虚线表示弱引用:
如上图,ThreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal没有外部强引用引用他,那么系统gc的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远没法回收,形成内存泄露。
只有当前thread结束之后, Thread Ref就不会存在栈中,强引用断开, Thread, ThreadLocalMap, Entry将所有被GC回收。但若是是线程对象不被回收的状况,好比使用线程池,线程结束是不会销毁的,就可能出现真正意义上的内存泄露。
ThreadLocalMap设计时的对上面问题的对策:
当咱们仔细读过ThreadLocalMap的源码,咱们能够推断,若是在使用的ThreadLocal的过程当中,显式地进行remove是个很好的编码习惯,这样是不会引发内存泄漏。
那么若是没有显式地进行remove呢?只能说若是对应线程以后调用ThreadLocal的get和set方法都有很高的几率会顺便清理掉无效对象,断开value强引用,从而大对象被收集器回收。
但不管如何,咱们应该考虑到什么时候调用ThreadLocal的remove方法。一个比较熟悉的场景就是对于一个请求一个线程的server如tomcat,在代码中对web api做一个切面,存放一些如用户名等用户信息,在链接点方法结束后,再显式调用remove。
启动两个线程,第一个线程中存储的userid为1,第二个线程中存储的userid为2。
package com.lzumetal.multithread.threadlocal; import java.math.BigDecimal; public class ThreadLocalTest { public static void main(String[] args) throws InterruptedException { Order order01 = new Order(1, 1, new BigDecimal(10), 1); new Thread(new OrderHandler(1, order01)).start(); Order order02 = new Order(2, 2, new BigDecimal(20), 2); new Thread(new OrderHandler(2, order02)).start(); } }
package com.lzumetal.multithread.threadlocal; public class OrderHandler implements Runnable { private static OrderService orderService = new OrderService(); private Integer userId; private Order order; public OrderHandler(Integer userId, Order order) { this.userId = userId; this.order = order; } @Override public void run() { EnvUtil.getUserIdContext().set(userId); orderService.addOrder(order); orderService.updateStock(order.getGoodId(), order.getGoodCount()); } }
package com.lzumetal.multithread.threadlocal; public class OrderService { /** * 新增订单 * * @param order */ public void addOrder(Order order) { Integer userId = EnvUtil.getUserIdContext().get(); System.out.println(Thread.currentThread().getName() + "新增订单服务中获取用户id-->" + userId); } /** * 更新库存 * * @param goodId * @param goodCount */ public void updateStock(Integer goodId, Integer goodCount) { //虽然更新库存不须要关注userId,可是在这里也同样可以获取到 Integer userId = EnvUtil.getUserIdContext().get(); System.out.println(Thread.currentThread().getName() + "在更新库存中获取用户id-->" + userId); } }
Thread-0新增订单服务中获取用户id-->1 Thread-1新增订单服务中获取用户id-->2 Thread-0在更新库存中获取用户id-->1 Thread-1在更新库存中获取用户id-->2