ThreadLocal 是一个线程安全副本,用于储存仅容许当前线程能访问/修改的值,不知从什么时候起看到了”线程安全“这种字眼就会不自觉想到性能问题,可是ThreadLocal是实现线程安全的另一种方案"空间换时间"。数组
先看2个小Demo安全
使用ThreadLocalide
public class ThreadLoacalTest{ // 定义线程安全副本 private final static ThreadLocal<Integer> THREAD_NUMBER = new ThreadLocal<Integer>(); // 继承Thread重写run() static class ThreadTest extends Thread{ @Override public void run() { for (int i=0; i < 3 ;i++){ // 若是THREAD_NUMBER 为null,赋值0,不然+1 THREAD_NUMBER.set(THREAD_NUMBER.get() == null ? 0 : THREAD_NUMBER.get() + 1); // 打印信息 System.out.println(Thread.currentThread().getName() + ":" + THREAD_NUMBER.get()); } THREAD_NUMBER.remove(); } } public static void main(String[] args) { ThreadTest t1 = new ThreadTest(); ThreadTest t2 = new ThreadTest(); ThreadTest t3 = new ThreadTest(); t1.start(); t2.start(); t3.start(); } }
不使用ThreadLocal性能
public class Test { // 定义普一般量 public static Integer THREAD_NUMBER = 0; // 继承Thread重写run() static class ThreadTest extends Thread{ @Override public void run() { for (int i=0; i < 3 ;i++){ // 打印信息 System.out.println(Thread.currentThread().getName() + ":" + THREAD_NUMBER++); } } } public static void main(String[] args) { ThreadTest t1 = new ThreadTest(); ThreadTest t2 = new ThreadTest(); ThreadTest t3 = new ThreadTest(); t1.start(); t2.start(); t3.start(); } }
从上面的打印结果能够看出,使用了ThreadLocal的常量不会与其余线程共享,而没有使用ThreadLocal的常量是会与其余线程共享学习
接下来看源码this
从set()开始吧spa
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
1.先获取当前线程对象线程
2.经过当前线程对象获取一个ThreadLocalMap 实例(如下简称map)3d
3.若是map不为null 则直接向map插入数据对象
4.若是为null则调用createMap()建立一个map而且向map插入数据
该map以当前对象做为key,这样咱们就只须要关注value而不用维护key
getMap()
能够看到这个threadLocals属性是Thread的,可是类是属于ThreadLocal的一个内部类ThreadLocalMap
由于这个map对象属于Thread的实例,每一个Thread都是特有的map,因此能提供整个线程使用且不与其余线程共享
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocal.ThreadLocalMap threadLocals = null;
createMap()
2个参数,一个是key(当前ThreadLocal实例),一个是value(咱们维护的对象)
下面代码能够看出值都是保存在一个Entry对象的数组里面,以及一些初始化的工做
Entry又是ThreadLocal.ThreadLocalMap 的一个内部类 ThreadLocal.ThreadLocalMap.Entry
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.set()
在这里向Entry[]添加元素,计算出下标,若是该下标位置没有元素,则直接插入元素。若是有元素,则遍历数组直到没有元素的下标位置才停下进行存储,若是遇到相同key则更新元素而且遇到key为null的Entry时,会删除元素。存储完以后判断当前数组容量是否须要扩容,若是进行了扩容,全部元素都会从新存储一遍
private void set(ThreadLocal<?> key, Object value) {
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();
}
接下来看看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(); }
咱们获取ThreadLocal维护的变量都是直接经过ThreadLocal的get()获取
1.获取当前线程对象
2.经过当前线程对象获取map
3.经过当前对象向map获取值(Entry对象),若是map与Entry对象都不为null则直接返回存储的值
4.若是值为null,则调用setInitialValue()初始化键值对并返回null
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; }
setInitialValue() 先经过initialValue()初始化值,而后保存到map而且返回初始化的值
(判断是否须要建立map这个步骤实在不想写了,ThreadLocal里面太多这样的操做了)
protected T initialValue() {
return null;
}
ThreadLocalMap.getEntry()
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
首先是计算出key对应值储存的下标,当元素不为null且key相等,则返回对应的值,若是下标与key不对应,则遍历数组查询,整个数组都不存在该key,则返回null,若是遇到key为null的Entry时,会删除元素
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) { Entry[] tab = table; int len = tab.length; while (e != null) { ThreadLocal<?> k = e.get(); if (k == key) return e; if (k == null) expungeStaleEntry(i); else i = nextIndex(i, len); e = tab[i]; } return null; }
以上源码学习总结:
1.ThreadLocal原理是该类主要操做一个Thread类的 ThreadLocalMap threadLocals属性,该属性是属于每一个Thread实例特有的,因此能提供整个线程使用且不与其余线程共享
2.ThreadLocalMap 的底层实际上是一个素组,当计算到的存储下标已存在元素,则循环判断是否为null(可否存储),若是存在相同key则更新元素
3.get()、set()操做的时候若是碰到了key为null的状况都会删除key为null的 Entry 对象
都说ThreadLocal是以"空间换时间",以上代码也证明了确实如此。可是有个问题,就是空间用得越多,就越容易OOM。因此Entry继承了WeakReference弱引用。
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
这里的key是弱引用,就是key指向的对象(ThreadLocal)若是没有其余强引用的状况下,下次GC的时候就会被回收,这保证了必定的垃圾回收效率,可是若是存在其余强引用状况下,GC并不会回收该对象,因此咱们使用ThreadLocal的时候须要注意有没有被强引用