1、使用场景
线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。
代码示例:
public class Demo27 {
private static final ThreadLocal<String> myThreadLocal = ThreadLocal.withInitial(() -> "This is the initial value");
public static void main(String[] args) {
for (int i = 0; i < 6; i++){
new Thread(new MyRunnable(), "线程"+i).start();
}
}
public static class MyRunnable implements Runnable {
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name + "的threadLocal"+ ",设置为" + name);
myThreadLocal.set(name);
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
System.out.println(name + "的值是:" + myThreadLocal.get());
}
}
}
输出:
线程0的threadLocal,设置为线程0
线程1的threadLocal,设置为线程1
线程5的threadLocal,设置为线程5
线程4的threadLocal,设置为线程4
线程3的threadLocal,设置为线程3
线程2的threadLocal,设置为线程2
线程3的值是:线程3
线程2的值是:线程2
线程4的值是:线程4
线程5的值是:线程5
线程0的值是:线程0
线程1的值是:线程1
Process finished with exit code 0
2、原理
JDK1.7的设计
JDK1.8的设计
JDK1.8的设计方案有两个好处:
1、每个Map存储的Entry数量变少,减少内存占用
2、当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存的占用
代码执行流程
注意:ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现的
关键代码
set()
public void set(T value) {
// 获得当前线程,只是为了获取当前线程维护的ThreadLocalMap,
// 并不是把当前线程当做map的key!
Thread t = Thread.currentThread();
// 获得当前线程的私有map
ThreadLocalMap map = getMap(t);
if (map != null)
// 给线程私有的ThreadLocalMap里面存放数据,key为this,
// 也就是当前的ThreadLocal类,value为传传入的值
map.set(this, value);
else
createMap(t, value);
}
// getMap方法的实现
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 这里的threadLocals属于Thread类,类型为map
ThreadLocal.ThreadLocalMap threadLocals = null;
// 注意看这里的构造函数,key就是threadLocals,不是线程本身!
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// 这是set操作的代码
private void set(ThreadLocal<?> key, Object value) {
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();
}
get()
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象维护的threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 以当前的threadLocal为key获取对应的存储实体
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取对应实体对应的value
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
// 此方法可以被子类重写。否则默认返回null
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
常见问题
threadLocalMap是什么时候初始化的
第一次调用get()或者set()方法的时候,如果没有的话会执行initValue()方法进行初始化
threadLocalMap什么时候扩容
threadLocalMap的默认容量为16,阈值为0.75,当数量超过0.75的时候会进行remove()操作,如果remove之后仍然超过阈值,则进行扩容。
如何进行扩容
首先会创建新的数组,长度是当前的两倍,然后迭代老的数组,将其中的数据重新按照hash算法放入新的数组里面,然后更新threadLocalMap对象的这个散列表的引用,指向新的数组。
如何解决hash冲突
在根据key计算index的时候,定义了一个AutomicInteger类型的值,每次获取当前值并加上HASH_INCREMENT,HASH_INCREMENT=0x61c88647(这个值跟斐波那契数列有关),主要目的是为了让hash值均匀分布在2的n次方的数组里,尽量避免hash冲突。