ThreadLocal是什么
- ThreadLocal是一个能够隔离线程的数据存储类。特定线程存储的数据只有该特定线程才能够获取到。
ThreadLocal的原理
低版本中
ThreadLocal中最重要的就是set和get方法,我们一起来看一下
set
public void set(T value) {
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values == null) {
values = initializeValues(currentThread);
}
values.put(this, value);
}
看一下values和initializeValues方法
Values values(Thread current) {
return current.localValues;
}
...
Values initializeValues(Thread current) {
return current.localValues = new Values();
}
localValues引用是关键
/**
* Normal thread local values.
*/
ThreadLocal.Values localValues;
我们可以发现,每个线程里面都含有一个ThreadLocal.Values的引用,这个就是ThreadLocal能够隔离线程的最重要的原因
我们接着看value是怎么存储的
void put(ThreadLocal<?> key, Object value) {
cleanUp();
// Keep track of first tombstone. That's where we want to go back
// and add an entry if necessary.
int firstTombstone = -1;
for (int index = key.hash & mask;; index = next(index)) {
Object k = table[index];
if (k == key.reference) {
// Replace existing entry.
table[index + 1] = value;
return;
}
if (k == null) {
if (firstTombstone == -1) {
// Fill in null slot.
table[index] = key.reference;
table[index + 1] = value;
size++;
return;
}
// Go back and replace first tombstone.
table[firstTombstone] = key.reference;
table[firstTombstone + 1] = value;
tombstones--;
size++;
return;
}
// Remember first tombstone.
if (firstTombstone == -1 && k == TOMBSTONE) {
firstTombstone = index;
}
}
}
可以看到,每个线程有一个ThreadLocal.Values的引用,而ThreadLocal.Values这个类里面有个
/**
* Map entries. Contains alternating keys (ThreadLocal) and values.
* The length is always a power of 2.
*/
private Object[] table;
- 而ThreadLocal和ThreadLocal包含的value就是通过key.reference放在index,而value放在index+1的位置来识别的。而存放位置则是使用了类似HashMap的存储原理。
get
public T get() {
// Optimized for the fast path.
Thread currentThread = Thread.currentThread();
Values values = values(currentThread);
if (values != null) {
Object[] table = values.table;
int index = hash & values.mask;
if (this.reference == table[index]) {
return (T) table[index + 1];
}
} else {
values = initializeValues(currentThread);
}
return (T) values.getAfterMiss(this);
}
可以看到,这里的操作很简单,就是使用了类似HashMap的原理,通过hash值来计算下标,进而获取value
- 这里有一个需要注意的点是,这里的哈希冲突的解决是通过存储的时候通过特殊方式使生成的hash不会冲突了来解决的。
这样,ThreadLocal的原理就讲解完成了,但是ThreadLocal在高版本发生了许多变化,我们一起来看一下
高版本
set和get
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
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代替了Values
我们一起看看这两者有什么区别
ThreadLocalMap替换了Values
重点看一下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();
}
可以发现这里主要有两个区别,
- 一个是Object[]被替换成了Entry[]
- 而是这里的存储彻底使用了HashMap的存储原理
高版本的变化是为了什么?
- 在我看来,主要是为了更好的解决Hash冲突,低版本中,每个ThreadLocal需要占用table的两个字段,而高版本中只需要占用一个,这个改变极大的减低了Hash冲突带来的效率降低。
以上内容属于笔者看源码的所得,可能有些地方不一定完全正确,欢迎大家指导交流