ThreadLocal是java线程中的局部变量,变量作用域仅在当前线程有效,是线程安全的。ThreadLocal常用于在多线程中,各自线程保存自己私有的变量值,如会话管理等场景。
ThreadLocal类定义及常用方法如下:
public class ThreadLocal<T> {
protected T initialValue() //初始化方法,protected方法,用于子类重写;
public void set(T value) //设置值;
public T get() //获取值;
public void remove() //清空值。当线程回收时,局部变量也会自动回收,主动调起是非必须操作,只是加快回收速度;
}
ThreadLocal在多线程中是线程安全的,原因是在每个线程中,都维持着自己私有的类型为ThreadLocal.ThreadLocalMap的变量threadLocals。
Thread类关键源码:
//Thread类中threadLocals变量, 维护在ThreadLocal中
ThreadLocal.ThreadLocalMap threadLocals = null;
即是Thread类每个实例都有自己独立的threadLocal变量,理所当然线程是安全的。
TheadLocal类的主要操作方法源码如下:
//获取值
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();
}
//初始化
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 set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
//获取线程关联的ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//创建ThreadLocalMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
分析上述操作方法,可以发现所有读写操作依赖于ThreadLocal.ThreadLocalMap内部类。
ThreadLocal.ThreadLocalMap内部类,维护着每个线程自己的多个ThreadLocal变量, key为当前的threadLocal实例(ThreadLocal本身不存值,只是引用),value为设置的值 (值存放在ThreadLocal.ThreadLocalMap.Entry实例中)。关键定义如下:
static class ThreadLocalMap {
//存储数据的数组
private Entry[] table;
//获取Entry,key的类型为ThreadLocal
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
}
ThreadLocal.ThreadLocalMap.Entry内部类,是存储数据的实际数据结构,继承WeakReference,因此是个弱引用,当虚拟机垃圾回收时,无论内存是否足够,都会被回收。 源码如下:
static class Entry extends WeakReference<ThreadLocal<?>> {
/ The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
到此,ThreadLocal能实现线程安全,私有,脉络很清晰了。实质是,变量存在于各个线程空间内存中,各自调自己的内存空间变量,互不影响。
使用ThreadLocal要注意内存泄露,使用完后,要主动调用remove()方法。原因:
import lombok.Data;
import org.apache.commons.lang3.RandomUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class ThreadLocalStudy {
public static void main(String[] args) {
//线程一,定义自己的变量001
Runnable t1 = () -> {
try {
StuContext.get().setStuNo("001");
System.out.println("t1: set stuNo 001" );
TimeUnit.SECONDS.sleep(RandomUtils.nextInt(0, 3));
System.out.println("t1: get " + StuContext.get().getStuNo());
} catch (InterruptedException e) {
}finally {
StuContext.stuCtx.remove();
System.out.println("t1: remove stuCtx" );
}
};
//线程二,定义自己的变量002
Runnable t2 = () -> {
try {
StuContext.get().setStuNo("002");
System.out.println("t2: set stuNo 002" );
TimeUnit.SECONDS.sleep(RandomUtils.nextInt(0, 3));
System.out.println("t2: get " + StuContext.get().getStuNo());
} catch (InterruptedException e) {
}finally {
StuContext.stuCtx.remove();
System.out.println("t2: remove stuCtx" );
}
};
//使用线程池执行线程t1,t2
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(t1);
executor.submit(t2);
executor.shutdown();
}
//定义stu上下文类
@Data
public static class StuContext {
public static ThreadLocal<StuContext> stuCtx = new ThreadLocal<>();
private String stuNo;
//获取上下文
public static StuContext get() {
if (null == stuCtx.get()) {
//初始化上下文
stuCtx.set(new StuContext());
}
return stuCtx.get();
}
}
}
输出:
t2: set stuNo 002
t1: set stuNo 001
t2: get 002
t2: remove stuCtx
t1: get 001
t1: remove stuCtx
结果分析:从上面输出可以看出,t1和t2线程在各自的线程内操作TheadLocal变量stuCtx,相互间不干扰。