此系列文章是参考《JAVA并发编程从入门到精通》一书写的一些读后笔记,其中也会进行扩展补充,写的不准确的地方还望广大同胞指出,大家一起学习,一起码奴。
ThreadLocal是个什么东西
ThreadLocal为每个使用该变量的线程提供独立的变量副本,就是说每个线程都可以独立地改变自己的副本,而不会影响其他的线程。从线程的角度来看,目标变量就像是线程的本地变量,即ThreadLocal
使用ThreadLocal的简单示例
public class ThreadLocalTest extends Thread{
//1.通过匿名内部类覆盖ThreadLocal的initialValue方法,指定初始值
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue() {
return 0;
}
};
//2.获取下一个序列值
public Integer getNextNum() {
seqNum.set(seqNum.get() + 1);
return seqNum.get();
}
@Override
public void run() {
//3.三次循环输出线程的名称以及seqNum参数值
for (int i = 0; i < 3 ; i++){
System.out.println(
String.format("threaName : %s , seqNum = %s",
Thread.currentThread().getName(),getNextNum()));
}
}
public static void main(String[] args) {
ThreadLocalTest threadLocalTest = new ThreadLocalTest();
//4.启动三个线程共享ThreadLocalTest,各自产生序列号
Thread thread1 = new Thread(threadLocalTest);
Thread thread2 = new Thread(threadLocalTest);
Thread thread3 = new Thread(threadLocalTest);
thread1.start();
thread2.start();
thread3.start();
}
}
程序的运行结果如下:
threadName = Thread-2 , seqNum = 1
threadName = Thread-3 , seqNum = 1
threadName = Thread-1 , seqNum = 1
threadName = Thread-3 , seqNum = 2
threadName = Thread-1 , seqNum = 2
threadName = Thread-3 , seqNum = 3
threadName = Thread-1 , seqNum = 3
threadName = Thread-2 , seqNum = 2
threadName = Thread-2 , seqNum = 3
我们会发现三个线程共享同一个ThreadLocalTest实例,但没有互相干扰的情况,而是各自产生独立的序列号,这就是因为我们通过ThreadLocal为每一个线程提供了单独的副本。
ThreadLocal的常用方法们
- void set(T value) :设置当前线程的局部变量的值
public void set(T value) {
//1.获取当前线程Thread对象
Thread t = Thread.currentThread();
//2.根据thread对象获取ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);//3.将当前对象与value存入ThreadLocalMap中
else
createMap(t, value); //4.创建一个ThreadLocalMap
}
从上面的set方法源码中可以看出,TheadLocal存储value其实是靠ThreadLocalMap去做的,保证了线程独立的变量也是ThreadLocalMap施的魔法,下面来看下上个代码片段中出现的方法。
//只是做了获取Thead的threadLocals属性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建一个ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
//ThreadLocalMap其实是ThreadLocal的一个静态内部类
static class ThreadLocalMap {
//ThreadLocalMap提供的有参构造器
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//初始化一个Entry数组,INITIAL_CAPACITY默认容量16
table = new Entry[INITIAL_CAPACITY];
//计算该元素在数组中的下标
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//通过ThreadLocal,value构造Entry对象,放入table数组中
table[i] = new Entry(firstKey, firstValue);
size = 1;
//计算下次数组多大时进行resize,并赋值给Threshold
setThreshold(INITIAL_CAPACITY);
}
}
上面这一段可以看出,Thread的局部变量threadLocals是一个ThreadLocalMap,也就是说线程的局部变量都会保存在ThreadLocalMap里面,而TheadLocalMap又是如何在保存变量的呢,其实就是由ThreadLocalMap的内部类Entry[]来完成的,而Entry中的key就是threadlocal对象,value就是我们传入的值。至此可以细细的品味下这样的设计逻辑,而且还有个点需要思考的就是,Entry将ThreadLocal设置为了弱引用是为了什么。
- public T get() :返回当前线程所对应的线程局部变量
//获取线程的局部变量
public T get() {
Thread t = Thread.currentThread();
//根据当前的Thread对象获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过ThreadLocal从ThreadLocalMap中获取到Entry的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
可以看出获取线程的局部变量值是通过map.getEntry(this)来完成,上文ThreadLocalMap构造器中可以看到Entry是怎么生成的了,所以这里就不用源码来解释怎么获取的了,下面我们再看setInitialValue()做了什么:
//这个方法的作用就是设置一个初始值,默认是null,方法与set()基本一直,只不过value不是参数而已
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;
}
- public void remove() :将当前局部变量的值删除
//ThreadLocal删除局部变量的值
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
//ThreadLocalMap的remove方法
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
//获取变量值在Entry[]数组的下标
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();
expungeStaleEntry(i);
return;
}
}
}
- protected T intialValue() :返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,这个方法是一个延迟调用方法,缺省实现是直接返回一个null
ThreadLocal的数据结构
- 每个Thread线程内部都有一个ThreadLocalMap
- ThreadLocalMap有一个Entry数组,数组元素里面存储线程本地对象(key)和线程的变量副本(value)
- Thread内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
注意点
- 在ThreadLocalMap内部类Entry是继承了WeakReference,也就说Entry被设置成了弱引用,那么在垃圾回收时key为null的Entry将会被回收掉
- ThreadLocal什么时候会导致内存泄漏
- 由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,如果当前线程再迟迟不结束,这些value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
- 其实ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value,但以下的情况还是会又内存泄漏的隐患:
1.使用static的ThreadLocal,延长了ThreadLocal的生命周期,可能导致的内存泄漏。
2.分配使用了ThreadLocal又不再调用get(),set(),remove()方法,那么就会导致内存泄漏。
- 在使用线程池的时候,ThreadLocal的使用要更为小心