ThreadLocal是用于提供支持线程间变量隔离,实现线程安全,多线程下变量同步访问有两种方式:1是通过Synchronized同步锁,多线程以排队的形式访问变量,保证线程安全,2是通过ThreadLocal实现线程间变量有多个副本,第一种方式是牺牲时间换安全,第二种是牺牲空间换安全,两种方式各有利弊。
ThrealdLocal的实现原理大致介绍下:
jdk中Thread类下有一个ThreadLocalMap类型的成员变量,而ThreaLocalMap类属于ThrealLocal类的一个静态内部类,ThreaLocalMap类的数据结构是一个Entry型的数组,而Entry类,又是ThreadLocalMap类的内部静态类,Entry类的数据结构是只有一个Object类型的成员变量,可以当作该Entry的一个具体的值value,其中需要注意的是,Entry内部类继承了弱引用接口,只有一个构造函数,其中有两个参数,第一个是一个弱类型的引用的参数,作为该Entry的标记,可以看作是该Entry的key,第二个是该Entry实际对应的value值。
前面提到Thread类下有一个ThreadLocalMap类型的成员变量,也就是说每个线程都有各自的ThreadLocalMap类型的对象,它们互相独立,这是ThreadLocal实现的关键,ThreadLocal每次取值,设置值的时候,都是在对每个线程自己的ThreadLocalMap对象进行操作,所以能互相独立,互不影响。
那对ThreadLocalMap如何进行get,set,刚才说到ThreadLocalMap内部就是一个Entry类型的数组,大家有没有想到HashMap内部类Entry,ThreadLocalMap的get set方法的实现和HashMap有很多相似之处,下面简单介绍ThreadLocalMap的存取实现逻辑。
先上ThreadLocal的set方法代码:
public void set(T value) {
//获取当前线程对象
Thread t = Thread.currentThread();
//根据当前线程获取当前线程下的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
//如果获取到了ThreadLocalMap,调用ThreadLocalMap的存值方法
map.set(this, value);
else
//如果没有获取到ThreadLocalMap对象,创建一个ThreadLocalMap对象
createMap(t, value);
}
ThreadLocal的set方法实际是调用了ThreadLocalMap的set方法,下面再看ThreadLocalMap类的set方法:
private void set(ThreadLocal<?> key, Object value) {
//获取ThreadLocalMap的静态内部类Entry对象数组
//该Entry数组就是ThreadLocalMap实际的数据结构
Entry[] tab = table;
//获取entry数组长度
int len = tab.length;
//获取当前ThreadLocal对象的hash值,并对数组长度取模,
//得出一个小于数组长度的值,作为新值存放的数组下标
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取数组中该元素Entry的key值
ThreadLocal<?> k = e.get();
if (k == key) {
//如果该Entry的key值和需要设置的key值地址相同
//那么赋予Entry一个新的value
e.value = value;
return;
}
if (k == null) {
//如果改Entry元素的key值为null,说名该Entry元素已经无法引用了
//回收该Entry(或者说清理该Entry占用的一个槽)
replaceStaleEntry(key, value, i);
return;
}
}
//执行到此处时,tab[i]一定为null,说明Entry数组的该位置没有对象
//那么new一个Enry对象,前面说过构造Entry需要两个参数,一个是弱类型引用
//还有一个时具体的值
tab[i] = new Entry(key, value);
//Entry的个数加一 注意 Entry数组的长度不等于个数,
//个数指的是不为null的元素的数量,长度是包含null元素的数量
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
//清理没有使用的Entry元素,如果没有清理成功且Entry个数大于阀值
//重新hash扩容
rehash();
}
这里有个Entry数组个数和阀值的比较,阀值是数组大小的三分之二,也就是说如果Entry非null元素的个数达到了数组长度的三分之二及以上,那么就需要hash扩容了。至于为什么是数组长度的三分之二,感兴趣可以去查。
nextIndex方法就是获取数组指定索引的下一个索引,如果下一个索引超过了数组的长度,就返回数组第一个索引0.
下面用一张图描述 Thread ThreadLocal TreadLocalMap Entry这几个主要类的关系: