ThreadLoca,线程本地变量。它为每一个使用该变量的线程都提供了独立的副本,通过它可以做到线程间的数据隔离,每个线程都可以访问各自内部的副本变量。
它本身能够被多个线程共享使用,ThreadLocal类提供了get(),set()方法,在不同的线程中调用它的set方法进行设值,值是保存在当前线程自己的"map"中的。每个线程都只能获取到自己的"map",因此只有自己才可以通过get来获取设置的值。通过这种方式很好的做到线程间的数据隔离。
ThreadLocal的几个重要方法
- set(T value) :将值保存到当前线程的map中。
- get() :从当前线程的map中获取值。
- initialValue():我们可以在创建ThreadLocal时重写该方法提供一个初始值,当没有set就直接get时候就获取该初始值。
- remove() 移除当前线程保存在map中的值。
使用:
public class Main {
public static void main(String[] args) {
ThreadLocal threadLocal=new ThreadLocal(){
@Override
protected Object initialValue() {
return 0;//提供一个初始值
}
};
new Thread(new Runnable() {
@Override
public void run() {
threadLocal.set(100);
System.out.println("获取"+Thread.currentThread().getName()+"保存的值:"+threadLocal.get());
}
}).start();
System.out.println("获取"+Thread.currentThread().getName()+"保存的值:"+threadLocal.get());
}
结果:
获取main保存的值:0
获取Thread-0保存的值:100
可以看到ThreadLocal的使用很简单,通过它的set方法我们可以进行设值,通过get方法可以获取set方法保存的值,各个线程通过ThreadLocal get到的值都是各自调用set保存的值,它们互不干扰,很好的做到了线程间的数据隔离。同样的,如果我们没有经过set就直接get获取到的就是我们重写的initialValue方法提供的初始值,如果我们不重写它,初始值就是null。
如果在一个线程中多次调用set方法,然后调用get方法会如何?
ThreadLocal threadLocal = new ThreadLocal();
threadLocal.set(100);
threadLocal.set(200);
threadLocal.set(300);
System.out.println(threadLocal.get()); //300
结论:get方法总是返回当前线程在调用set时设置的最新值。
通过源码看本质:
先看看threadLocal.set方法是如何保存数据的?
//ThreadLocal#set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
首先,获取到当前的线程对象,然后调用getMap方法获取当前线程对象的ThreadLocalMap,它是Thread类的一个成员,完全类似于HashMap,因此可以把它看作一个"map"。
//ThreadLocal#getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
如果获取到map,就调用它的set方法设值,以ThreadLocal对象为key。
如果map为null(第一次设值),就调用createMap方法创建一个"map",然后就值保存进去,以ThreadLocal对象为key。
如何通过createMap方法创建一个"map"呢?
ThreadLocal#createMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
可以看到,直接创建一个ThreadLocalMap对象,然后将它赋给当前线程的threadLocals成员。在创建"map"的时候同时就通过构造将值保存进去了。
ThreadLocalMap是ThreadLocal的静态内部类。
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内部又是通过Entry来保存数据的。
当map不为null就不用创建它了,会直接调用map.set(this, value)进行设值,看看它怎么做的?
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) {//1、如果键相同,就使用新数据替换旧数据
e.value = value;
return;
}
if (k == null) {//2、如果有Entry的key为null,就将其逐出,并用新数据替换。
replaceStaleEntry(key, value, i);
return;
}
}
//创建新的Entry,并且用ThreadLocal作为key,要保存的数据作为value
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
注释1解释了为什么get方法总是返回当前线程在调用set时设置的最新值,注释2是为了防止内存泄漏的。
threadLocal.set方法理解起来很简单,本质就是将数据保存到当前线程的"map"中,以ThreadLocal为key。每个线程都有自己的map成员,这样当然可以很好的做到线程间的数据隔离。
再来看看是如何获取保存的值?
//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();
}
很简单,首先获取当前的线程对象,然后调用getMap获取到当前线程对象持有的ThreadLocalMap,然后以ThreadLocal为key从"map"中获取Entry,前面已经知道Entry保存了我们的数据,获取到就直接返回。如果map为null或者Entry为空就需要调用setInitialValue()方法获取一个初始值。
//ThreadLocal #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;
}
先调用initialValue获取初始值,initialValue方法我们可以在创建ThreadLocal时重写,然后将初始值保存到map中,最后返回初始值。
补充:ThreadLocalMap通过Entry来保存数据,Entry是WeakReference的子类,从构造可以看出只有key是弱引用的,当JVM开始垃圾回收时,如果ThreadLocal和GC ROOTS之间不存在引用链就会被回收,此时key==null,但是需要注意value还是存在的。 下次再set数据时会进行检查,删除key为空的Entry,可以从一定程度上防止内存泄漏。比较好的习惯是用完后调用remove把数据移除,否则可能会产生泄漏,只有当前线程结束时,数据才会被释放。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
至于ThreadLocal的remove方法,就是直接将"map"中的数据移除。
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}