注:在《透彻理解Spring事务设计思想之手写实现》中,已经向你们揭示了Spring就是利用ThreadLocal来实现一个线程中的Connection是同一个,从而保证了事务。本篇博客将带你们来深刻分析ThreadLocal的实现原理。java
ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工做于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程分配一个独立的变量副本。因此每个线程均可以独立地改变本身的副本,而不会影响其余线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。
线程局部变量并非Java的新发明,不少语言(如IBM XL、FORTRAN)在语法层面就提供线程局部变量。在Java中没有提供语言级支持,而以一种变通的方法,经过ThreadLocal的类提供支持。因此,在Java中编写线程局部变量的代码相对来讲要笨拙一些,这也是为何线程局部变量没有在Java开发者中获得很好普及的缘由。 数组
归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式:访问串行化,对象共享化。而ThreadLocal采用了“以空间换时间”的方式:访问并行化,对象独享化。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。 安全
原理图:数据结构
ThreadLocal的实现是这样的:每一个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal实例自己,value 是真正须要存储的 Object。
也就是说 ThreadLocal 自己并不存储值,它只是做为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是图中的虚线,表示 ThreadLocalMap 是使用 ThreadLocal 的弱引用做为 Key 的,弱引用的对象在 GC 时会被回收。多线程
/* * 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); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } /* * 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; } /* * 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); } }
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;
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; } } /* * The initial capacity -- MUST be a power of two. */ private static final int INITIAL_CAPACITY = 16; /* * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; ... }
从名字上看,能够猜到它也是一个相似HashMap的数据结构,可是在ThreadLocal中,并没实现Map接口。在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每个key-value键值对,只不过这里的key永远都是ThreadLocal对象,是否是很神奇,经过ThreadLocal对象的set方法,结果把ThreadLocal对象本身当作key,放进了ThreadLoalMap中。ide
没有链表结构,那发生hash冲突了怎么办?函数
先看看ThreadLoalMap中插入一个key-value的实现this
/* * Set the value associated with key. * * @param key the thread local object * @param value the value to be set */ private void set(ThreadLocal<?> key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); if (k == key) { e.value = value; return; } if (k == null) { replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) { rehash(); } }
每一个ThreadLocal对象都有一个hash值 threadLocalHashCode
,每初始化一个ThreadLocal对象,hash值就增长一个固定的大小 0x61c88647
。spa
在插入过程当中,根据ThreadLocal对象的hash值,定位到table中的位置i,过程以下: 一、若是当前位置是空的,那么正好,就初始化一个Entry对象放在位置i上; 二、不巧,位置i已经有Entry对象了,若是这个Entry对象的key正好是即将设置的key,那么从新设置Entry中的value; 三、很不巧,位置i的Entry对象,和即将设置的key不要紧,那么只能找下一个空位置;线程
这样的话,在get的时候,也会根据ThreadLocal对象的hash值,定位到table中的位置,而后判断该位置Entry对象中的key是否和get的key一致,若是不一致,就判断下一个位置
能够发现,set和get若是冲突严重的话,效率很低,由于ThreadLoalMap是Thread的一个属性,因此即便在本身的代码中控制了设置的元素个数,但仍是不能控制其它代码的行为。
能够从ThreadLocal的get函数中看出来,其中getmap函数是用t做为参数,这里t就是当前执行的线程。
从而得知,get函数就是从当前线程的threadlocalmap中取出当前线程对应的变量的副本【注意,变量是保存在线程中的,而不是保存在ThreadLocal变量中】。当前线程中,有一个变量引用名字是threadLocals,这个引用是在ThreadLocal类中createmap函数内初始化的。每一个线程都有一个这样的threadLocals引用的ThreadLocalMap,以ThreadLocal和ThreadLocal对象声明的变量类型做为参数。这样,咱们所使用的ThreadLocal变量的实际数据,经过get函数取值的时候,就是经过取出Thread中threadLocals引用的map,而后从这个map中根据当前threadLocal做为参数,取出数据。
ThreadLocalMap使用ThreadLocal的弱引用做为key,若是一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,若是当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远没法回收,形成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种状况,也加上了一些防御措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里全部key为null的value。
可是这些被动的预防措施并不能保证不会内存泄漏:
使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能致使的内存泄漏(参考ThreadLocal 内存泄露的实例分析)。
分配使用了ThreadLocal又再也不调用get(),set(),remove()方法,那么就会致使内存泄漏。
从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会致使内存泄漏,可是另外一个问题也一样值得思考:为何使用弱引用而不是强引用?
咱们先来看看官方文档的说法:
To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对很是大和长时间的用途,哈希表使用弱引用的 key。
下面咱们分两种状况讨论:
key 使用强引用:引用的ThreadLocal的对象被回收了,可是ThreadLocalMap还持有ThreadLocal的强引用,若是没有手动删除,ThreadLocal不会被回收,致使Entry内存泄漏。
key 使用弱引用:引用的ThreadLocal的对象被回收了,因为ThreadLocalMap持有ThreadLocal的弱引用,即便没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
比较两种状况,咱们能够发现:因为ThreadLocalMap的生命周期跟Thread同样长,若是都没有手动删除对应key,都会致使内存泄漏,可是使用弱引用能够多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。
所以,ThreadLocal内存泄漏的根源是:因为ThreadLocalMap的生命周期跟Thread同样长,若是没有手动删除对应key就会致使内存泄漏,而不是由于弱引用。
每次使用完ThreadLocal,都调用它的remove()方法,清除数据。
在使用线程池的状况下,没有及时清理ThreadLocal,不只是内存泄漏的问题,更严重的是可能致使业务逻辑出现问题。因此,使用ThreadLocal就跟加锁完要解锁同样,用完就清理。
ThreadLocal在Spring中发挥着重要的做用,Spring使用ThreadLocal解决线程安全问题,在管理request做用域的Bean、事务管理、任务调度、AOP等模块都出现了它们的身影,起着举足轻重的做用。要想了解Spring事务管理的底层技术,ThreadLocal是必须攻克的山头堡。