ThreadLocal全面解析,带你一起看源码
一、ThreadLocal作用
该类提供线程本地变量。这些变量不同于它们的正常对应方,因为每个访问(通过其 get或set方法)都有自己独立初始化的变量副本。通俗一点就是存放在ThreadLocal的变量值作用于当前线程。每个线程获取到的不是同一个。
二、使用方法
public class Test {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
private void init() {
threadLocal.set("text");
threadLocal.get();
}
public static void main(String[] args){
threadLocal.set("MainThreadData");
String mainThreadLocalString = threadLocal.get();
System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+mainThreadLocalString);
secondThread.start();
thirdThread.start();
fourthThread.start();
}
public static Thread secondThread = new Thread("SecondThread"){
@Override
public void run() {
super.run();
threadLocal.set("SecondThreadData");
String secondThreadLocalData = threadLocal.get();
System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+secondThreadLocalData);
}
};
public static Thread thirdThread = new Thread("ThirdThread"){
@Override
public void run() {
super.run();
String thirdThreadLocalData = threadLocal.get();
System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+thirdThreadLocalData);
}
};
public static Thread fourthThread = new Thread("FourthThread"){
@Override
public void run() {
super.run();
threadLocal.set("FourthThreadData");
String fourthThreadLocalData = threadLocal.get();
System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+fourthThreadLocalData);
threadLocal.remove();
fourthThreadLocalData = threadLocal.get();
System.out.println("ThreadName:"+Thread.currentThread().getName().toString()+"---Data:"+fourthThreadLocalData);
}
};
}
//结果
ThreadName:main---Data:MainThreadData
ThreadName:ThirdThread---Data:null
ThreadName:FourthThread---Data:FourthThreadData
ThreadName:FourthThread---Data:null
ThreadName:SecondThread---Data:SecondThreadData
我们可以看到 我们在不同线程中设置set不同的值,用get方法得到的也只是当前线程set的那个值,与其他线程无关。如果当前线程没有set,直接调用get方法,默认值为null。通过remove方法我们可以去清除我们set的值。
三、源码分析
我们看到ThreadLocal主要有三个方法,set、get、remove、下面我们来分析一下每个方法,我们先来看一下set方法
/**
* 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);
}
我们可以看到set方法首先获取当前的Thread,并根据获取到的Thread调用getMap方法获取一个ThreadLocalMap,我们看一下getMap方法是怎么拿到ThreadLocalMap的
/**
* 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;
}
getMap只有一行代码很明显可以看出是调用当前线程Thread的成员变量threadLocals,我们到Thread类中可以看到这个成员变量,
ThreadLocal.ThreadLocalMap threadLocals = null;
我们先不管ThreadLocalMap是什么,我们先看一下他是怎么进行创建的。我们还是回到ThreadLocal的set方法中,我们可以看到 当threadLocalMap为空的时候会调用 createMap(t, value);方法把当前现成threadLocal和我们set的Value传递过去。
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
我们发先原来创建原来这么简单 只是new了一个ThreadLocalMap对象构造方法中传递this和我们set的Value,并把这个对象赋值给Thread的threadLocals。现在我们知道了原来我们的Thread中都会存放一个ThreadLocalMap。不同的Thread里面的ThreadLocalMap是不同的。我们看一下ThreadLocalMap方法并看一下ThreadLocalMap是干什么的,首先我们知道ThreadLocalMap是ThreadLocal的内部类。看一下构造方法。
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
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类型的数组。并根据当前ThreadLocal的hashCode&(INITIAL_CAPACITY-1)得到一个值作为数组角标,然后new一个Entry对象将我们的ThreadLocal和Value存放到Entry中再存放到数组中对应的位置上。是不是有点和HashMap 有些相似。到现在为止我们基本上已经了解当我们第一次set的时候,我们的ThreadLocal是怎么把我们的数据存放到单独线程中的。我们最后看一下Entry是一个什么,Entry是ThreadLocalMap中的内部类,继承自若引用WeakReference,并将我们的set的Value存放到Entry中,如果大家不了解若引用的相关知识,大家可以自行百度学习。这里就不再赘述了。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
接下来我们看一下第二次set,我们的ThreadLocal是怎么设置的,我们通过ThreadLocal中的set方法可以看到当我们第二次进来的时候 map不为空会调用ThreadLocalMap的set方法
/**
* 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的哈希值去找到我们在Entry数组中应该存放的位置,如果这个位置已经有Entry了我们拿这个Entry的get()也就是我们创建Entry时候的那个ThreadLoal和我们现在的ThreadLocal相比是不是同一个,如果是同一个,直接将我们得到的Entry的Value设置成我们set的Value,如果不是我们会向下寻找,因为我们的Entry是软引用,当我们垃圾回收期工作的时候有可能会被回收,我们就会调用replaceStaleEntry方法将我们的table数组进行清理,并在我们的Entry数组中找到一个空槽或者是和我们相同ThreadLoal的Entry的位置,将我们数据存放到数组里面。如果我们通过我们ThreadLocal哈希值算出的位置上没有Entry的话。就直接新new一个Entry放到对应位置。到此我们的set方法已经全部分析完了。有没有些小激动。接下来我们接着分析get方法
/**
* 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方法比较简单,和set一样我们只需要获取到当前线程的ThreadLocalMap,根据我们的ThreadLocal获取到我们存放Value的Entry拿到我们的Value就可以了。如果我们在get之前没有调用set,我们这里就找不到我们要的Entry,就会调用我们的setInitialValue方法
/**
* 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;
}
这个方法的主要目的是设置一个默认值。在这个方法中首先会调用initialValue,如果你没复写这个方法,默认返回的是null,并判断是否存在ThreadLocalMap如果有进行set方法。如果没有进行createMap方法创建Map存入我们的值。并返回我们的默认Value,这两个方法我们在文章的前面已经进行分析过了。相信小伙伴们已经明白了。如果我们想给我们的ThreadLocal设置一个默认值的话,就可以重写initialValue();这个方法哦。
最后。和大家说一下Entry继承弱引用的原因,Entry属于ThreadLocalMap,ThreadLocalMap属于Thread,当我们创建ThreadLocal的时候是强引用,我们用这个ThreadLocal存取数据的时候,Entry[]当中的我们当前ThreadLocal对应的Entry<ThreadLocal>这个弱引用是不会被垃圾回收的。因为我们的ThreadLocal还在被强引用连接着。但是当我们把我们的ThreadLocal置空的话,垃圾回收器进行回收的时候,我们Entry数组当中的Entry<ThreadLocal>就会一起被回收,因为没有强引用指向了,这里优化了内存的使用。提高了运行效率。你get到了么?