本文主要记载本人学习ThreadLocal时对源码的理解,如有不妥或错误,恳请各位指出。
ThreadLocal有哪些主要方法?
方法名 | 返回值 | 描述 |
---|---|---|
get() | T | 返回此线程局部变量的当前线程副本中的值 |
initialValue() | T | 返回此线程局部变量的当前线程的“初始值” |
remove() | void | 移除此线程局部变量当前线程的值 |
set(T value) | void | 将此线程局部变量的当前线程副本中的值设置为指定值 |
从get()方法入手:
//返回当前线程的副本值
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取Thread类的成员变量threadLocals
ThreadLocalMap map = getMap(t);
if (map != null) {
//从map中获取key值为当前ThreadLocal对象的引用
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//获取引用e的value值
T result = (T)e.value;
return result;
}
}
//如果map为null或e为null,执行setInitialValue()
return setInitialValue();
}
ThreadLocalMap类是ThreadLocal类的静态内部类,是一个定制的散列映射,专门用于维护线程局部变量。
Entry类是ThreadLocalMap类的静态内部类,继承WeakReference类,是一个弱引用,其中key为当前ThreadLocal对象,value就是线程局部变量的当前线程副本值。可以通过e.get()方法获取key值和e.value获取副本值。
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
/*ThreadLocalMap部分成员变量*/
//初始容量大小
private static final int INITIAL_CAPACITY = 16;
//Entry数组,存放当前线程的线程局部变量副本值
private Entry[] table;
//table中引用的数量
private int size = 0;
//构造函数
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化table
table = new Entry[INITIAL_CAPACITY];
//根据散列值获取存放下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//创建初始value的引用对象
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
......
}
继续分析get()方法:
ThreadLocalMap map = getMap(t);
调用getMap()获取当前线程的ThreadLocalMap对象,该方法如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
也就是说我们实际上是获取Thread类中的全局变量引用threadLocals。
public class Thread implements Runnable{
...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
接下来我们看看setInitialValue()
方法:
private T setInitialValue() {
//调用initialValue()方法,获取初始化value,默认为null,可以通过重写改变初始化值
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
//若map!=null,而e=map.getEntry(this)==null,将初始化值保存到map中
map.set(this, value);
else
//map==null,创建一个新的map,并保存初始化值
createMap(t, value);
return value;
}
根据get()方法获取不到value的原因(不存在key=this的引用,或者map为空),执行set(ThreadLocal<?> key, Object value)
方法或者createMap(Thread t, T firstValue)
方法。我们分别来看看这两个方法的实现。
private void set(ThreadLocal<?> key, Object value) {
//获取存放线程局部变量的引用数组
Entry[] tab = table;
//获取数组长度
int len = tab.length;
//根据散列值获取遍历的初始下标
int i = key.threadLocalHashCode & (len-1);
//从i下标开始遍历,实际上table是一个环形队列
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
//获取key
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
//替换key值为null的Entry
replaceStaleEntry(key, value, i);
return;
}
}
//向table中插入新的引用
tab[i] = new Entry(key, value);
//数量++
int sz = ++size;
//清除key==null的引用,防止内存泄漏
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
从源码可以分析出,set方法主要实现新增key-value的逻辑,首先通过遍历table,如果发现有key=null的引用,取代之,否则新建一个引用。
void createMap(Thread t, T firstValue) {
//初始化threadLocals
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
createMap方法就是简单调用ThreadLocalMap的构造方法。
接下来看看set()方法和remove()方法。
set()
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法逻辑比较简单,和setInitialValue()
类似。若map不为空,插入,否则先新建map。
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
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) {
//释放引用
e.clear();
//清除Entry对象
expungeStaleEntry(i);
return;
}
}
}
举一个实例
Student实体类
public class Student {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
ThreadLocalDemo类
public class ThreadLocalDemo implements Runnable{
//线程局部变量,重写initalValue方法
private final static ThreadLocal<Student> studentLocal=new ThreadLocal<Student>(){
@Override
protected Student initialValue() {
return new Student();
}
};
@Override
public void run() {
accessStudent();
}
//访问Student
private void accessStudent(){
System.out.println("当前线程"+Thread.currentThread().getName()+" 开始运行");
Student stu=getStudent();//从线程局部变量中获取Student副本值
stu.setAge(new Random().nextInt(100));//随机设置年龄
System.out.println("第一次访问 "+Thread.currentThread().getName()+" age="+stu.getAge());//第一次访问
stu=getStudent();
System.out.println("第二次访问 "+Thread.currentThread().getName()+" age="+stu.getAge());//第二次访问
}
//从线程局部变量中获取副本值
private Student getStudent(){
return studentLocal.get();
}
public static void main(String[] args) {
//测试线程
new Thread(new ThreadLocalDemo()).start();
new Thread(new ThreadLocalDemo()).start();
}
}
运行结果:
当前线程Thread-0 开始运行
当前线程Thread-1 开始运行
第一次访问 Thread-0 age=34
第一次访问 Thread-1 age=12
第二次访问 Thread-0 age=34
第二次访问 Thread-1 age=12
从结果中可以看到,两个线程各自保存了Student的副本值,实现数据的隔离。
我们再举一个例子帮助理解,看下图:
Step1:小红向枪支老板拿枪。相当于线程A调用ThreadLocalA的get()方法。
Step2:枪支老板发现小红是新客户,就创建一个属于小红的储物柜,然后拿出一把新枪放到小红的储物柜。相当于调用initialValue()方法初始化副本值,再调用createMap(Thread t, T firstValue)方法创建map,将初始值存入。
Step3:小红再次向枪支老板拿枪,枪支老板发现小红的储物柜已经有一把枪了,就把储物柜的枪给小红。相当于线程A第二次调用ThreadLocalA的get()方法,拿到的副本值和第一次是一样的。
Step4:小红突然觉得弓箭也挺有意思的,就向弓箭老板拿弓,弓箭老板找到了小红的储物柜,但是发现没有弓,就拿了一把新的弓并放到储物柜。相当于线程A调用ThreadLocalB的get()方法,取得map后,再调用 ThreadLocalMap.Entry e = map.getEntry(this);获取副本值的引用对象,但是e的值为空。所以调用setInitialValue()方法,获取初始副本值,然后调用map.set(this, value)保存初始副本值。
如果小明也和小红一样,那么小明也有了一个属于他自己的储物柜,里面放着小明专属的枪和弓。这就意味着小红和小明分别存放了线程局部变量的副本,线程间实现了数据隔离。
关于ThreadLocal的实际应用场景,可以看看这篇文章的应用场景部分。