ThreadLocal
一、ThreadLocal 介绍
1.1 ThreadLocal 是什么?
ThreadLocal 叫做线程变量,在 ThreadLocal 中填充的变量属于 当前 线程,该变量对其他线程而言是隔离的。ThreadLocal 为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
1.2 ThreadLocal特性
ThreadLocal 和 Synchronized 都是为了解决多线程中相同变量的访问冲突问题,不同的点是
- Synchronized 通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal 是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于 Synchronized,ThreadLocal 具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
1.3 使用场景
- 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
- 线程间数据隔离
- 进行事务操作,用于存储线程事务信息
- 数据库连接,Session会话管理
1.4 ThreadLocal 方法介绍
// 设置值
void set(T value);
// 获取值
T get();
// 删除值
void remove();
// 设置默认缺省值
T initialValue();
二、ThreadLocal 的使用
2.1 实例一: ThreadLocal 返回默认值
public static void main(String[] args) {
ThreadLocal<Integer> defaultValueLocal = new ThreadLocal() {
public Integer initialValue() {
return 1;
}
};
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
final Integer result = defaultValueLocal.get();
System.out.println(Thread.currentThread().getName()+ " --> result=" + result);
}
});
thread.start();
}
}
控制台输出:
Thread-0 --> result=1
Thread-1 --> result=1
Thread-2 --> result=1
Thread-3 --> result=1
Thread-4 --> result=1
Thread-5 --> result=1
Thread-6 --> result=1
Thread-7 --> result=1
Thread-8 --> result=1
Thread-9 --> result=1
2.2 实例二:set 设置值
public static void main(String[] args) {
ThreadLocal<String> nameLocal = new ThreadLocal<>();
for (int i = 0; i < 10; i++) {
int finalI = i;
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
nameLocal.set(Thread.currentThread().getName() + " -->" + finalI);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程输出:" + nameLocal.get());
}
});
thread.start();
}
}
控制台输出:
当前线程输出:Thread-2 -->2
当前线程输出:Thread-1 -->1
当前线程输出:Thread-0 -->0
当前线程输出:Thread-3 -->3
当前线程输出:Thread-9 -->9
当前线程输出:Thread-8 -->8
当前线程输出:Thread-7 -->7
当前线程输出:Thread-6 -->6
当前线程输出:Thread-5 -->5
当前线程输出:Thread-4 -->4
三、源码
3.1 set 方法
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 判断 ThreadLocalMap 对象是否存在
if (map != null)
// 存在,则设置值到 ThreadLocalMap 中
map.set(this, value);
else
// 否则创建 ThreadLocalMap 对象
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
**ThreadLocalMap ** 对象是 ThreadLocal 的一个静态内部类,里面定义了一个 Entry 来保存数据,而且还是继承的弱引用。在 Entry 内部使用 ThreadLocal 作为key,使用我们设置的 value 作为 value:
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
3.2 get方法
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取 ThreadLocalMap 对象
ThreadLocalMap map = getMap(t);
// 判断 ThreadLocalMap 对象是否存在
if (map != null) {
// 获取当前线程存储变量
ThreadLocalMap.Entry e = map.getEntry(this);
// 变量不为空时,将值返回
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
// ThreadLocalMap 对象不存在或不存在指定KEY值时,返回默认值
return setInitialValue();
}
// 设置初始默认值
private T setInitialValue() {
// 调用初始默认值设置函数
T value = initialValue();
// 将初始默认值存储到 ThreadLocalMap 对象中
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
3.3 initialValue 方法
protected T initialValue() {
// 默认返回 null,按需覆盖
return null;
}
四、ThreadLocal 内存泄漏风险
在上面我们介绍 ThreadLocal 源码的时候,了解到 ThreadLocal 通过 ThreadLocalMap 对象存储值,它的 key 存储的是当前的 ThreadLocal 的引用,属于弱引用。当 ThreadLocal 被回收时,value 却还存在,就会造成内存泄漏。
解决办法: 在使用完 ThreadLocal 后,执行 remove 操作,避免出现内存溢出情况。