一、引入:为什么要使用ThreadLocal
之前,在某位大牛的一篇文章中,看到了实现线程安全的几个层次,分别是:
1、使用无状态的对象
无状态对象也就是不变的对象,它是最安全的,因此不需要考虑线程间同步等安全性问题;
2、做到线程封闭
线程封闭就是把对象封装到单个线程里,只有这一个线程才能看到该对象;
3、采用同步技术
比如采用synchronized关键字,锁定某个类、某个方法或者某个代码块,或者使用互斥锁、读写锁等锁技术,最根本的就是让某个对象,或者某个方法,或者某个代码块,在某一时刻,始终只能有一个线程操纵。
对于第一个使用无状态的对象,它的应用场景非常有限,而大型复杂系统,往往需要大量有状态的对象;而对于第三个采用同步技术,它的实现比较复杂,需要考虑线程间同步问题,稍有不慎,就会写出错误的代码,带来毁灭性的打击,并且,线程间同步技术需要使用锁等,使得很多线程可能处于等待状态,会带来性能方面的消耗。
我们今天单说下线程封闭。何谓线程封闭?线程封闭就是把对象封装到单个线程里,只有这一个线程才能看到该对象。它可以通过以下三种方式实现:
1、ad-hoc线程封闭;
2、栈封闭;
3、使用ThreadLocal。
ad-hoc线程封闭就是完全靠实现者控制的线程封闭,这个我们今天不说。而栈封闭虽然概念显得高大上,其实就一句话,多使用局部变量。为什么呢?这就要从JVM的运行时内存模型说起,局部变量的引用是保持在线程栈中的,只对当前线程可见,其他线程不可见,所以说局部变量是线程安全的。而第三种方式--使用ThreadLocal,那么问题来了,ThreadLocal是什么,它又是如何实现线程封闭的呢?
二、入题:什么是ThreadLocal
ThreadLocal有一个名称,叫做线程本地变量。它为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。由此可知,它采用了一种空间换时间的策略,来保证线程间的安全性。它实际上也是程序控制线程封闭的一种机制,不过这种机制是Java自身提供的罢了。
三、理解:ThreadLocal内部是如何实现的?
既然ThreadLocal为变量在每个线程中都创建了一个副本,那么我们先来看下它是如何存取的。在此之间,我们先介绍下线程Thread类中一个十分重要的成员变量threadLocals,其定义如下:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;它是一个ThreadLocal.ThreadLocalMap类型的对象,也就是ThreadLocal内部类ThreadLocalMap的实例。而ThreadLocalMap是ThreadLocal的一个静态内部类,也就是说,它不需要依靠外部类的实例化而实例化。这在 《答群友问:Java静态内部类、普通内部类等的理解》一文中已经提到过了。那么ThreadLocalMap是什么呢?里面存储的是什么东西呢?先来看下它的构造方法,如下:
/** * Construct a new map initially containing (firstKey, firstValue). * ThreadLocalMaps are constructed lazily, so we only create * one when we have at least one entry to put in it. */ ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); }实际上,ThreadLocalMap就是一种Map结构的数据类型。它的Key为ThreadLocal实例,而value则是任意一个Object。而ThreadLocalMap中存储key-value的是其内部类Entry,定义如下:
/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */ static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }它继承自WeakReference,是对ThreadLocal实例的一种弱引用。而弱引用的作用其实很简单,当一个对象仅仅被weak reference指向, 而没有任何其他strong reference指向的时候, 如果GC运行, 那么这个对象就会被回收。
好了,言归正传。我们还是先说下ThreadLocal是如何为变量在每个线程中都创建了一个副本,也就是它的存取方法。
先看存,存是依靠ThreadLocal的set()方法实现的,代码如下:
/** * 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) { // 获取当前线程t Thread t = Thread.currentThread(); // 调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map // ThreadLocalMap中存储的是ThreadLocal实例到value的映射, // value就是我们想在线程中持有的变量副本 ThreadLocalMap map = getMap(t); if (map != null)// 如果map存在 // 将当前ThreadLocal实例this与传入的value映射关系放入map map.set(this, value); else// 如果map不存在 // 调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值 createMap(t, value); }逻辑比较简单,大体流程如下:
1、获取当前线程t;
2、调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map:
ThreadLocalMap中存储的是ThreadLocal实例到value的映射,而value就是我们想在线程中持有的变量副本;
3、判断map是否存在:
3.1、如果map存在,将当前ThreadLocal实例this与传入的value映射关系放入map;
3.2、如果map不存在,调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值。
我们先看下getMap()方法,代码如下:
/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }getMap()方法其实获取就是上面我们提到过的对应线程t的threadLocals变量。
接下来,我们再看下createMap()方法,代码如下:
/** * 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); }createMap()方法就是构造一个ThreadLocalMap实例,赋值给线程t的threadLocals变量。而构造ThreadLocalMap实例时,传入的key为当前ThreadLocal实例this,value则是我们想在线程中持有的变量副本。
存数据讲完了,我们再看下取数据。而取数据则是通过ThreadLocal的get()方法实现的,代码如下:
/** * 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() { // 获得当前线程t Thread t = Thread.currentThread(); // 调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map // ThreadLocalMap中存储的是ThreadLocal实例到value的映射, // value就是我们想在线程中持有的变量副本 ThreadLocalMap map = getMap(t); // 如果map不为空 if (map != null) { // 利用当前ThreadLocal实例this,从map中获取对应的条目Entry,即e, // Entry的key就是ThreadLocal实例this,value对应为实际需要的value ThreadLocalMap.Entry e = map.getEntry(this); // 如果e不为空,直接返回e中的value,并转化为T类型 if (e != null) return (T)e.value; } // 如果map为空,调用setInitialValue()返回value return setInitialValue(); }取数据的get()方法也比较简单,大体逻辑如下:
1、获得当前线程t;
2、调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map:
ThreadLocalMap中存储的是ThreadLocal实例到value的映射,而value就是我们想在线程中持有的变量副本;
3、判断map是否为空:
3.1、如果map不为空,利用当前ThreadLocal实例this,从map中获取对应的条目Entry,即e,Entry的key就是ThreadLocal实例this,value对应为实际需要的value;如果e不为空,直接返回e中的value,并转化为T类型;
3.2、如果map为空,调用setInitialValue()返回value。
getMap()方法我们在上面已经讲解过了,这里我们只看下setInitialValue()方法,代码如下:
/** * 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() { // 调用initialValue()初始化value,空方法,返回的为null,可以根据需要进行重写 T value = initialValue(); // 获取当前线程t Thread t = Thread.currentThread(); // 调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map // ThreadLocalMap中存储的是ThreadLocal实例到value的映射, // value就是我们想在线程中持有的变量副本 ThreadLocalMap map = getMap(t); if (map != null)// 如果map存在 // 将当前ThreadLocal实例this与初始化的value放入map map.set(this, value); else// 如果map不存在 // 调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值 createMap(t, value); return value; }setInitialValue()方法,说白了,就是设置初始化的value值。它的处理流程如下:
1、调用initialValue()初始化value,initialValue是一个空方法,返回的为null,可以根据需要进行重写;
2、获取当前线程t;
3、调用getMap()方法,获得当前线程t对应的ThreadLocalMap,即map:
ThreadLocalMap中存储的是ThreadLocal实例到value的映射,而value就是我们想在线程中持有的变量副本;
4、判断map是否存在:
4.1、如果map存在:将当前ThreadLocal实例this与初始化的value放入map;
4.2、如果map不存在:调用createMap()方法,创建当前线程t对应的ThreadLocalMap,并加入value值;
5、返回value。
其中,createMap()方法上面已经介绍过了,这里就不再赘述了。