原文地址:点击打开链接,中间内容有些自己学习时的笔记。。。
一,两个问题
1.什么是ThreadLocal?
ThreadLocal类可以理解为线程本地变量,即如果定义了一个ThreadLocal,每个线程往这个ThreadLocal中读写是线程隔离的,互相之间不会影响的,它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制
2.实现的大致思路
- 每个线程内部会维护一个类似HashMap对象,称为ThreadLocalMap,里面包含了若干Entry(K-V键值对),相应的线程被称为这些Entry的属主线程
- Entry的key是一个ThreadLocal实例,Value是一个线程特有对象,entry的作用是为其属主线程建立起一个ThreadLocal实例和一个线程特有对象之间的对应关系
- Entry对Key的引用是弱引用,对Value的引用时强引用
二,ThreadLocal的API
有如下几个方法
public T get(){}//获取ThreadLocal在当前线程中保存的变量副本
public void set(T value){}//设置当前线程中变量的副本
public void remove(){}//移除当前线程变量的副本
protected T initialValue(){}//一般用来在使用时进行重写,是一个延迟加载的方法
其中,get的源码实现,第一句是取得当前线程,然后通过getMap(t)获取一个map,map类型为ThreadLocalMap,接着获取到<key,value>键值对,这里传进去的是this而是是当前线程t。如果获取成功则返回value的值,如果map为空,则调用setInitialValue方法返回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
三,ThreadLocalMap的源码解读
1.存储结构
既然是个map(和java.util.map不一样,这里指概念上的map),当然要有自己的key和value,可以将其简单视为key是ThreadLocal,value为实际放入的值,之所以说是简单视为,因为实际上ThreadLocal中存放的是ThreadLocal的弱引用
ThreadLocalMap中结点的定义:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
Entry便是ThreadLocalMap里定义的结点,继承了WeakReference类,定义了一个类型为Object的value,用于存放塞到ThreadLocal里的值
2.为什么要弱引用
如果这里使用普通的key-value形式定义存储结构,实质上就会造成结点的声明周期与线程强绑定,只要线程没有销毁,那么结点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理结点,弱引用是java中四挡引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC,当某个ThreadLocal已将没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry键值会失效,为ThreadLocalMap本身的垃圾清理提供了便利
3.类成员变量和相应方法
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
/**
* The table, resized as necessary.
* table.length MUST always be a power of two.
*/
private Entry[] table;
/**
* The number of entries in the table.
*/
private int size = 0;
/**
* The next size value at which to resize.
*/
private int threshold; // Default to 0
可以看到ThreadLocalMap维护了一个Entry数组,并且要求大小为2的幂,同时记录表里面entry的个数以及下一次需要扩容的阈值
/*设置resize阈值以维持最坏2/3的装载因子
* Set the resize threshold to maintain at worst a 2/3 load factor.
*/
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
/**
* Increment i modulo len.环形意义的下一个索引
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.环形意义的上一个索引
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
ThreadLocalMap使用线性探测法来解决散列冲突,所以实际上Entry数组在程序逻辑上作为一个环形存在的
4.构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//初始化table数组
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);//firstKey.threadLocalHashCode与初始大小16取模得到哈希值
table[i] = new Entry(firstKey, firstValue);//初始化该结点
size = 1;//设置节点表大小为1
setThreshold(INITIAL_CAPACITY);//设定扩容阈值
}
源码看起来真是一件有趣又无聊的事情,未完,持续更新。。。