简单介绍线程内部存储数据存储类 ThreadLocal
ThreadLocal的诞生或者说ThreadLocal要解决的问题
有些时候一个线程中的任务过于复杂,这可能表现为函数调用栈比较深以及代码入口
的多样性, 在这种情况下我们又需要监听器能够贯穿整个线程的执行过程,这个时候
可以怎么做呢?其实这时就可以采用ThreadLocal,采用ThreadLocal可以让监听器
作为线程内的全局对象而存在,在线程内部只要通过get方法就可以获取到监听器。如
果不采用ThreadLocal,那么我们能想到的可能是如下两种方法:第一种方法是将监
听器通过参数的形式在函数调用栈中进行传递,第二种方法就是将监听器作为静态变量
供线程访问。上述这两种方法都是有局限性的。 第一种方法的问题是当函数调用栈很
看起来很糟糕。第二种方法是可以接受的,但是这种状态是不具有可扩充性的,比如同
时 有两个线程在执行,那么就需要提供两个静态的监听器对象,如果有10个线程在并
发执 呢?提供10个静态的监听器对象?这显然是不可思议的,而采用ThreadLocal,
每个 监听器对象都在自己的线程内部存储,根本就不会有方法2的这种问题。
ThreadLocal 使用案例:
public class MyClass {
private static ThreadLocal<Boolean> mBooleanThreadLocal = new ThreadLocal<>();
public static void main(String[] args){
System.out.println("main");
//向线程#Main中存储一个true值
mBooleanThreadLocal.set(true);
System.out.println("main#" +mBooleanThreadLocal.get());
test();
}
public static void test(){
new Thread("Thread#1"){
@Override
public void run() {
//向线程thread#1中存储一个false值
mBooleanThreadLocal.set(false);
System.out.println("[Thread#1]"+mBooleanThreadLocal.get());
}
}.start();
new Thread("Thread#2"){
@Override
public void run() {
//打印thread#2中存储的默认值
System.out.println("[Thread#2]"+mBooleanThreadLocal.get());
}
}.start();
}
}
运行结果为:
main
main#true
[Thread#2]null
[Thread#1]false
可以看出在主线程中ThreadLocal 存储的是true,Thread#1线程中存储的为false,Thread#2线程存储的为null。
让我们来看看是怎么回事
源码解析
查看Thread.java 可以看到存在一个成员变量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
这个成员变量就是用来保存线程运行期间的全局数据的。
让我们来看看 ThreadLocalMap 是一个什么东西,看官方介绍ThreadLocalMap是一个自定义的hashmap.
那我们看看这个threadLocals 是什么时候初始化的
void createMap(Thread t, T firstValue) {
//初始化当前线程的ThreadLocalMap成员变量
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
让我们再看看这个方法什么时候被调用的
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//初始化
createMap(t, value);
}
可以看到这个方法就是我们在例子中mBooleanThreadLocal调用的方法
再来看看是怎样取数据的,也是我们调用mBooleanThreadLocal.get()方法做了什么事
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals 实例对象
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取到对应的Entry,key为当前的LocalThread对象 value为我们存储的值。
//这个时候我们应该思考一下是否会存在内存泄漏的问题
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
关于ThreadLocal的内存泄漏风险参考
结论:为了避免内存泄漏我们最好 调用一下ThreadLocal提供的remove方法
mBooleanThreadLocal.remove();
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.
*/
大概意思就是ThreadLocalMap里面是一个自定义的HashMap,这个数据结构里面存储的值是当前线程私有的
供这个线程全局使用,用于存储一些生命周期比较长的值,这个数据结构里面的保存这个一个Entry对象key是使用
WeakReferences 包裹着,保证其能及时的释放。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
在ThreadLocalMap中存在一个成员变量table
private Entry[] table;
初始化:
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);
}
存
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();
}
总结:这符合Java的万物皆对象的思想,线程也是一个类,new Thread()也是在创建了一个线程的实例对象,就像我们平常创建的一个自定义View实例一样,有时候我们为了分层让代码便于维护会在这个自定义的View中维护一个集合或者数组,如果外部数据有变化,通过我们对外暴露的方法更新数据源,这个ThreadLocal就相当于自定义View中的数据源,这是java封装性的一种思想。