每个线程都有一个ThreadLocal线程本地变量,各个线程本地变量互不干扰。TreadLocalMap类型的变量(该类是一个轻量级的Map),可以调用set(),get()方法存取值,可以贯穿整个线程生命周期。键为当前线程的id,值为Object类型。
作用:提供一个线程内公共变量,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,让线程的本地变量进行隔离。
使用ThreadLocal示例:
创建ThreadLocal,定义threadlocal存储值的类型,并且可以进行初始化。通过threadLocal.get()获取值,threadLocal.set(s)设置值。
public class ThreadLocalUse {
//创建threadLocal时并初始化
private static ThreadLocal threadLocal = new ThreadLocal() {
@Nullable
@Override
protected Integer initialValue() {
return 1;
}
};
public static void test() {
Thread[] threads = new Thread[3];
for (int i=0;i value: " + threadLocal.get());
}
}
}
调用ThreadLocalUse.test()启动线程,打印:
thread0 threadLocal -> value: 1
thread1 threadLocal -> value: 2
thread2 threadLocal -> value: 3
3个threadLocal存储初始值都是1,加上各自的id,分别为0,1,2,然后就打印出了1,2,3的各个值。3个ThreadLocal保存的值各不影响,各有3份副本。
不适用Threadlocal来存储的示例:
public class UseThreadLocal {
static Integer threadLocal = new Integer(1);
public void start() {
Thread[] threads = new Thread[3];
for (int i=0;i value: " + threadLocal);
}
}
}
打印的结果:
本来该打印1,2,3的,结果打印了1,2,4,数据就错乱了。
源码:
进入ThreadLocal的get方法:
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 getMap(Thread t) {
return t.threadLocals;
}
获取当前线程然后调用了getMap(Thread t) 方法,调用了当前线程的threadLocals。进入之后,得到了ThreadLocalMap类型的成员变量,每个线程都有一个自己的ThreadLocalMap。
ThreadLocalMap类部分代码:
static class ThreadLocalMap {
private static final int INITIAL_CAPACITY = 16;
private ThreadLocal.ThreadLocalMap.Entry[] table;
private int size;
private int threshold;
private void setThreshold(int var1) {
this.threshold = var1 * 2 / 3;
}
private static int nextIndex(int var0, int var1) {
return var0 + 1 < var1 ? var0 + 1 : 0;
}
private static int prevIndex(int var0, int var1) {
return var0 - 1 >= 0 ? var0 - 1 : var1 - 1;
}
ThreadLocalMap(ThreadLocal> var1, Object var2) {
this.size = 0;
this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
int var3 = var1.threadLocalHashCode & 15;
this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
this.size = 1;
this.setThreshold(16);
}
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> var1, Object var2) {
super(var1);
this.value = var2;
}
}
}
它有一个静态内部类Entry,保存一个value,数据存储在Entry类型的数组中,获取entry的value需要传入threadLocal来作为key进行获取。
那么根据源码画出示意图:
创建一个静态的ThreadLocal变量,然后创建多个线程,通过threadLocal变量存和取数据。当调用threadLocal.set时,判断当前线程的threadLocalMap是否为空,为空去new一个ThreadLocalMap对象,存入到当前线程的threadLocals。不为空,则获取threadLocalMap调用set方法,当前ThreadLocal对象为键,存入值。当调用threadLocal.get方法时,同样先判断当前线程的threadLocalMap是否为空,不为空调用threadLocalMap的getEntry方法,使用当前ThreadLocal对象为键,取出entry对象里面的value。这样完成整个操作。
从图中可以看到,每个线程的存取,都是对自己内部唯一的threadLocalMap进行操作,ThreadLocal对象只是作为了键来存取数据,根本就没有用到多线程的操作。
ThreadLocal引发的内存泄漏
static class Entry extends WeakReference> {
Object value;
Entry(ThreadLocal> var1, Object var2) {
super(var1);
this.value = var2;
}
}
由于这个ThreadLocal使用weakreference来包装的,所以在内存不足时,threadlocal会被回收,而threadlocalMap在线程中是强引用,一旦threadlocal被回收,在threadlocalmap中对应的value就无法再被访问,就成了垃圾,且不可被回收,就发生了内存泄漏。要解决这个问题,在用完了threadloacal之后,需要调用threadlocal的remove方法,将threadlocal所指向的value一起从内存中清除。
代码地址:
https://github.com/running-libo/JavaPrinciples