导语
ThreadLocal做为Thread中的一个成员变量,可以储存当前线程所对应的值
ThreadLocal的简单例子
public static void main(String[] args) {
final ThreadLocal<String> tl = new ThreadLocal<>();
new Thread(){
public void run(){
tl.set("new thread");
String str = tl.get();
System.out.println("子线程"+str);
};
}.start();
System.out.println("主线程"+tl.get());
tl.set("main thread");
System.out.println("主线程2"+tl.get());
}
运行结果:
主线程null
子线程new thread
主线程2main thread
说明:在不同的线程中,ThreadLocal所取的值是不一样的,与相应的线程有紧密的关系。
用法大家可以去别的地方看看,本位重点是讨论实现原理。
读完本篇文章,你应该理解的内容
如果你读完本篇,脑海中会浮现上图,说明你对于ThreadLocal已经理解了。
Thread中用来储存数据的容器
在Thread的成员变量中,是有ThreadLocal相关的引用的
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal在不同线程中的值是区分的,那是因为不同的Thread所引用的ThreadLocal.ThreadLocalMap中的
值是不一样的,其中ThreadLocalMap就是来储存数据的容器,可以用来区分不同的ThreadLocal
ThreadLocal源码的分析
set()方法
//设置value
public void set(T value) {
//获取当前的线程对象
Thread t = Thread.currentThread();
//获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
//判断map是否为null
if (map != null)
//不为空,当值放入map中
map.set(this, value);
else
//为空,创建并把值放入map中
createMap(t, value);
}
//根据线程对象获取相应的map
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建map并且与当前的ThreadLocal建立联系
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
说明:set()中的思路很清晰,就是将数据保存在Thread的容器ThreadLocalMap中。
get()方法
//获取数据
public T get() {
//获取当前的线程对象
Thread t = Thread.currentThread();
//获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
//map不为空的处理
if (map != null) {
//获取相应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//获取数据,并转换类型
@SuppressWarnings("unchecked")
T result = (T)e.value;
//返回结果
return result;
}
}
//map为空的处理,返回默认的数据null
return setInitialValue();
}
说明:思路也同样清晰,获取当前Thread中的容器ThreadLocalMap,再取出相应的值。
remove()方法
//移除数据
public void remove() {
//获取当前线程所对应的ThreadLocalMap
ThreadLocalMap m = getMap(Thread.currentThread());
//移除相应的数据
if (m != null)
m.remove(this);
}
说明:同样很简单,获取当前Thread中的容器ThreadLocalMap,再移除相应的值。
小结
从上面的方法分析可以看出,ThreadLocal的实现原理是非常的简单,无非就是与Thread建立了联系,从而可以
区分不同的Thread
成员变量
//ThreadLocal的hashCode,每次创建的ThreadLocal,它的值一般不同
private final int threadLocalHashCode = nextHashCode();
//hashCode,原子操作
private static AtomicInteger nextHashCode =
new AtomicInteger();
//nextHashCode增长的大小,这个是一个黄金比的数字
//用途:即将整数尽可能均匀地散列到[0, 2^32-1]这个范围当中去,最大限度地减少冲
private static final int HASH_INCREMENT = 0x61c88647;
//获取下一个hashCode
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
说明:上述几个方法中,ThreadLocalMap都是通过ThreadLocal来作为key来区分不同的ThreadLocal。
而threadLocalHashCode做为标签,用来区分不同的ThreadLocal,每次初始化ThreadLocal,threadLocalHashCode
都是不一样的,会均匀地散列到[0, 2^32-1]这个范围中
ThreadLocalMap
如果一个线程中有多个ThreadLocal对象,那么是通过ThreadLocalMap来区分不同的ThreadLocal。
成员变量
//初始化的容量,必须是2的指数
private static final int INITIAL_CAPACITY = 16;
//用来储存数据的容器,长度必须是2的指数
private Entry[] table;
//数组的长度
private int size = 0;
//阈值,默认是0
private int threshold; // Default to 0
//Entry继承了弱引用:当长时间没有引用,key会变为Null
//在一些操作下会清空Entry,这样就会节省空间
static class Entry extends WeakReference<ThreadLocal<?>> {
//真正用来封装数据
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
说明:ThreadLocalMap的数据结构是个数组。每一个槽中是个Entry,里面封装了ThreadLocal的引用和对应的数据。
构造方法
//构造方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化数组
table = new Entry[INITIAL_CAPACITY];
//获取i:i是均匀地分散在[0,INITIAL_CAPACITY-1]之间的
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//将数据填充到对应的位置
table[i] = new Entry(firstKey, firstValue);
//更新size为1
size = 1;
//更新阈值:16 * 2 / 3
setThreshold(INITIAL_CAPACITY);
}
说明:通过ThreadLocal中的threadLocalHashCode来确定数组中的位置,将Value保存到对应的位置上
set()方法
//添加数据
private void set(ThreadLocal<?> key, Object value) {
//获取数组
Entry[] tab = table;
//数组的长度
int len = tab.length;
//获取对应的i
int i = key.threadLocalHashCode & (len-1);
//循环遍历,找到对应的Entry,更新其中的数据
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取当前的k
ThreadLocal<?> k = e.get();
//与key匹配的话,更新数据
if (k == key) {
e.value = value;
return;
}
//k为null的处理,长时间没有使用,弱引用已经将数据回收了
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
//当前的ThreadLocal是第一次添加数据
//创建Entry,并赋值
tab[i] = new Entry(key, value);
//更新size
int sz = ++size;
//如果没有移除数据并且更新后的size大于阈值,调用rehash()
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//会将数组的容器扩充->key.threadLocalHashCode & (len-1)的值也会变化
//所以会创建一个新的数组,将旧数组中相应的数据赋值到对应的位置
rehash();
}
get()方法
//根据ThreadLocal获取相应的Entry
private Entry getEntry(ThreadLocal<?> key) {
//获取i
int i = key.threadLocalHashCode & (table.length - 1);
//根据i从数组中获取相应的Entry
Entry e = table[i];
//判断Entry中是否储存了数据
if (e != null && e.get() == key)
//储存了数据,返回相应的结果
return e;
else
//数据丢失的处理:
//e为null,直接返回null
//e不为null,因为长时间未引用,key没了,需要清除下空的引用
return getEntryAfterMiss(key, i, e);
}
说明:get()方法比较简单,从数组中对应的位置找出数据并返回。
remove()方法
//通过Key来移除Entry
private void remove(ThreadLocal<?> key) {
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)]) {
if (e.get() == key) {
//找打了对应的Entry,清除数据
e.clear();
//清空槽
expungeStaleEntry(i);
return;
}
}
}
说明:remove()方法同样简单,找出数组中对应的位置,移除数据
关于均匀分布的直观体现
运行下面的Java代码,可以感觉下均匀分布
public class Main {
public static void main(String[] args) {
int index = 0;
int capacity = 16;
printIndex(capacity, index);
index = 0;
capacity = 32;
printIndex(capacity, index);
index = 0;
capacity = 64;
printIndex(capacity, index);
}
private static void printIndex(int capacity,int index){
for (int i = 0; i < 2 * capacity / 3; i++) {
System.out.print(" " + ((capacity - 1) & index) + " ");
index = next(index);
}
System.out.println("换行");
}
private static int next(int x) {
return x + 1640531527;
}
}
运行结果
0 7 14 5 12 3 10 1 8 15 换行
0 7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 换行
0 7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 换行
结语
- 通过上面的分析,我们可以看到,ThreadLocal有点像Thread当中的一个属性,是通过ThreadLocalMap来建立连接的,一个Thread可以连接多个ThreadLocal
- ThreadLocal最牛X的地方在于:在识别不同的ThreadLocal的同时,运用数学定理将数据均匀地分布在数组中
- 一定要理解文章开头给出的图片,这里再给一张不对称的图:
点这里看大图
代码可以看不懂,0x61c88647可以忽略,但上图一定要有印象
参考资料
1. HASH_INCREMENT为何确定为0x61c88647?:http://hehaiqian.com/2015/07/25/hash1-0x61c88647/
转载请标明出处http://blog.youkuaiyun.com/qq_26411333/article/details/51689018