Java中的ThreadLocal类容许咱们建立只能被同一个线程读写的变量。所以,若是一段代码含有一个ThreadLocal变量的引用,即便两个线程同时执行这段代码,它们也没法访问到对方的ThreadLocal变量。java
private ThreadLocal myThreadLocal = new ThreadLocal();
咱们能够看到,经过这段代码实例化了一个ThreadLocal对象。咱们只须要实例化对象一次,而且也不须要知道它是被哪一个线程实例化。虽然全部的线程都能访问到这个ThreadLocal实例,可是每一个线程却只能访问到本身经过调用ThreadLocal的set()方法设置的值。即便是两个不一样的线程在同一个ThreadLocal对象上设置了不一样的值,他们仍然没法访问到对方的值。并发
一旦建立了一个ThreadLocal变量,你能够经过以下代码设置某个须要保存的值:ide
myThreadLocal.set("A thread local value”);
能够经过下面方法读取保存在ThreadLocal变量中的值:函数
String threadLocalValue = (String) myThreadLocal.get();
咱们能够建立一个指定泛型类型的ThreadLocal对象,这样咱们就不须要每次对使用get()方法返回的值做强制类型转换了。下面展现了指定泛型类型的ThreadLocal例子:ui
private ThreadLocal myThreadLocal = new ThreadLocal<String>();
如今咱们只能往ThreadLocal对象中存入String类型的值了。而且咱们从ThreadLocal中获取值的时候也不须要强制类型转换了。this
因为在ThreadLocal对象中设置的值只能被设置这个值的线程访问到,线程没法在ThreadLocal对象上使用set()方法保存一个初始值,而且这个初始值能被全部线程访问到。spa
可是咱们能够经过建立一个ThreadLocal的子类而且重写initialValue()方法,来为一个ThreadLocal对象指定一个初始值。就像下面代码展现的那样:线程
private ThreadLocal myThreadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "This is the initial value"; } };
不一样的线程局部变量,好比说声明了n个(n>=2)这样的线程局部变量threadlocal,那么在Thread中的threadlocals中是怎么存储的呢?threadlocalmap中是怎么操做的?code
在ThreadLocal的set函数中,能够看到,其中的map.set(this, value);把当前的threadlocal传入到map中做为key,也就是说,在不一样的线程的threadlocals变量中,都会有一个以你所声明的那个线程局部变量threadlocal做为键的key-value。假设说声明了N个这样的线程局部变量变量,那么在线程的ThreadLocalMap中就会有n个分别以你的线程局部变量做为key的键值对。orm
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
在set方法内部咱们看到,首先经过getMap(Thread t)方法获取一个和当前线程相关的ThreadLocalMap,而后将变量的值设置到这个ThreadLocalMap对象中,固然若是获取到的ThreadLocalMap对象为空,就经过createMap方法建立。
线程隔离的秘密,就在于ThreadLocalMap这个类。
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap { /** * 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; } } ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } /** * Get the entry associated with key. This method * itself handles only the fast path: a direct hit of existing * key. It otherwise relays to getEntryAfterMiss. This is * designed to maximize performance for direct hits, in part * by making this method readily inlinable. * * @param key the thread local object * @return the entry associated with key, or null if no such */ private ThreadLocal.ThreadLocalMap.Entry getEntry(ThreadLocal key) { int i = key.threadLocalHashCode & (table.length - 1); ThreadLocal.ThreadLocalMap.Entry e = table[i]; if (e != null && e.get() == key) return e; else return getEntryAfterMiss(key, i, e); } }
ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(ThreadLocalMap is a customized hash map suitable only for maintaining thread local values.),每一个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类经过操做每个线程特有的ThreadLocalMap副本,从而实现了变量访问在不一样线程中的隔离。由于每一个线程的变量都是本身特有的,彻底不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象,而值就是你所设置的对象了。
为了加深理解,咱们接着看上面代码中出现的getMap和createMap方法的实现:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }
代码已经说的很是直白,就是获取和设置Thread内的一个叫threadLocals的变量,而这个变量的类型就是ThreadLocalMap,这样进一步验证了上文中的观点:每一个线程都有本身独立的ThreadLocalMap对象。打开java.lang.Thread类的源代码,咱们能获得更直观的证实:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
那么接下来再看一下ThreadLocal类中的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(); } 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; }
这两个方法的代码告诉咱们,在获取和当前线程绑定的值时,ThreadLocalMap对象是以this指向的ThreadLocal对象为键进行查找的,这固然和前面set()方法的代码是相呼应的。
设置到这些线程中的隔离变量,会不会致使内存泄漏呢?ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将做为Thread实例的垃圾被回收掉,因此彻底不用担忧内存泄漏的问题。
最后再提一句,ThreadLocal变量的这种隔离策略,也不是任何状况下都能使用的。若是多个线程并发访问的对象实例只容许也只能建立那么一个,那就没有别的办法了,老老实实的使用同步机制来访问吧。
==========END==========