源码解析
ThreadLocal是一个让每个线程都可以存储自己单独的一个变量副本,每个线程只能存储一个变量副本,那么其内部是怎么实现的呢,我们来看看分析下源码
首先其内部是有一个静态类 ThreadLocalMap,这也是最重要的一个部分,首先这个内部类还有一个内部类Entry
// 这个主要就是存储ThreadLocal的,类似key value进行存储
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
其中主要的一个成员变量是,说明了ThreadLocal底层其实是一个Entry数组,其存储的数据都是放到这个里面的
private Entry[] table;
我们主要来看看ThreadLocal 中 set,get和remove方法是怎么实现的
set方法
public void set(T value) {
//获取当前的线程
Thread t = Thread.currentThread();
//把t放入getMap中,返回一个ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
//如果不为空
if (map != null)
//直接存储到map中,this表示ThreadLocal创建出来的对象本身,value就是要存储的值
map.set(this, value);
else
//否则new一个新的ThreadLocalMap对象存储
createMap(t, value);
}
上面主要是调用了这个 map.set(this, value) 方法,下面是源码
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
//进行hash运算得出要存储在table数组的下标
int i = key.threadLocalHashCode & (len-1);
//判断当前的Entry是否存储为null,为null直接越过for循环
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//首先尝试去获取原本的table有没有存储对应的key值
ThreadLocal<?> k = e.get();
if (k == key) {
//如果有,直接替换返回
e.value = value;
return;
}
//如果为空,也就是没有,
if (k == null) {
//那么直接存储到table中
replaceStaleEntry(key, value, i);
return;
}
}
//直接new一个新的Entry存储到table数组中
tab[i] = new Entry(key, value);
//长度加1
int sz = ++size;
//如果满了就对table进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
get方法
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//得到对应的map
ThreadLocalMap map = getMap(t);
//如果不为空
if (map != null) {
//得到对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//返回value值
T result = (T)e.value;
return result;
}
}
//如果没有的话,就返回null
return setInitialValue();
}
remove方法
public void remove() {
//根据当前线程得到对应的map
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//如果不为空就把ThreadLocal对象本身删除
m.remove(this);
}
下面是一个简单使用例子
public class LocalDemo {
public static ThreadLocal<String> str = new InheritableThreadLocal<>();
public static void main(String[] args) {
str.set("123");
ThreadLocal local=new ThreadLocal();
Thread p = new Thread(
new Runnable() {
@Override
public void run() {
str.set("234");
System.out.println(str.get());
}
}
);
p.start();
try {
p.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(str.get());
}
}
//运行结果
234
123
可以看到,返回的结果并不一样。
总结
ThreadLocal内部先是判断当前线程,然后根据当前线程获取到变量,因此不同线程获取到的变量都是不一样的,并且每个ThreadLocal只能保存一个变量副本,如果想要一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。