ThreadLocal在解读
ThreadLocal 字面意思是本地线程
源码里给的说明大概如下:
这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问每个线程(通过其
get
或set
方法)都有自己独立初始化的变量副本。ThreadLocal
实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。只要线程存活并且
ThreadLocal
实例可以访问,每个线程都保存对其线程局部变量副本的隐式引用; 线程消失后,线程本地实例的所有副本都将被垃圾收集(除非存在对这些副本的其他引用)。
ThreadLocal主要的三大方法和一个内部静态类:get 、set 、remove、ThreadLocalMap,接下来让我们一起根据源码进行分析一下。
ThreadLocalMap
由于这三大方法都用到了ThreadLocalMap
,我们先来阅读一下ThreadLocalMap
的源码
官方说明大意:ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。在ThreadLocal类之外不导出任何操作。类是包私有的,允许在类线程中声明字段。为了帮助处理非常大和长寿命的用法,哈希表条目使用weakreference作为键。但是,由于不使用引用队列,因此只有当表开始耗尽空间时,才能保证删除过时的条目。
static class ThreadLocalMap {
//此哈希映射中的条目扩展WeakReference,使用它的主ref字段作为键(始终是ThreadLocal对象)。
//注意,空键(即entry.get()==null)意味着不再引用该键,
//因此可以从表中展开该项。在下面的代码中,这些条目被称为“过时的条目”。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//构造一个最初包含(firstKey,firstValue)的新映射。
//ThreadLocalMaps是延迟构建的,因此我们只有在至少有一个条目要放入其中时才创建一个。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//这里也可以看出 ThreadLocalMap 底层是维护了一个Entry数组
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 方法
首先让我们来看一下set的源码
public void set(T value) {
//获取访问的线程
Thread t = Thread.currentThread();
//根据访问的线程确定是否存在
ThreadLocalMap map = getMap(t);
//存在就直接替换value,否则创建
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
private void set(ThreadLocal<?> key, Object value) {
//我们不像get()那样使用快速路径,因为使用set()创建新条目至少和替换现有条目一样常见,在这种情况下,快速路径失败的频率更高。
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();
//根据key替换value
if (k == key) {
e.value = value;
return;
}
//替换就的entry
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
理解了set方法,get方法就很容易了
//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();
}
//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);
}
ThreadLocal特性
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
- Synchronized是通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。