ThreadLocal是什么?
ThreadLocal
是用来维护线程中的变量不被其他线程干扰而出现的一个结构,内部包含一个ThreadLocalMap
类,该类为Thread
类的一个局部变量,该Map
存储的key
为ThreadLocal
对象自身,value
为我们要存储的对象,这样一来,在不同线程中,持有的其实是当前线程的变量副本,与其他线程完全隔离,以此来保证线程执行过程中不受其他线程的影响。
ThreadLocal的原理
ThreadLocal
有4个public
的方法,以及一个内部类ThreadLocalMap
,里面存储的是Entry
数组。4个公有方法中,一个是初始化用的,另外三个get(),set(),remove()
是对数据的处理;操作的数据结构是ThreadLocalMap
,我们先来看一段代码
假设如下场景
- 我们给每个线程生成一个ID。
- 一旦设置,线程生命周期内不可变化。
- 容器活动期间不可以生成重复的ID
我们创建一个ThreadLocal管理类
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadLocalSample
{
public static void main(String[] args) {
incrementSameThreadId();
new Thread(new Runnable() {
@Override
public void run() {
incrementSameThreadId();
}
},"1").start();
new Thread(new Runnable() {
@Override
public void run() {
incrementSameThreadId();
}
},"2").start();
}
private static void incrementSameThreadId(){
try{
for (int i = 0; i <5; i++) {
System.out.println(Thread.currentThread()+"_"+i+",threadId:"+ThreadLocalId.get());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
ThreadLocalId.remove();
}
}
}
class ThreadLocalId {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>()
{
@Override
protected Integer initialValue(){//get方法时会调用
System.out.println(Thread.currentThread()+":initialValue");
return nextId.getAndIncrement();
}
};
static int get(){
return threadId.get();
}
static void remove(){
threadId.remove();
}
}
可以看到,不同线程输出的ThreadLocal
保存值是独立,它到底是怎么做到的呢?
get方法
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();
}
get
方法时ThreadLocal
的核心方法之一,其核心是根据调用的线程来获取该线程保存的值,我觉得这就是为什么ThreadLocal
能在不同线程返回不同的值的原因,也是ThreadLocal
为什么天然是线程安全的原因。从调用的线程中找到要返回的值
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);
}
构造的原理和get方法差不多,这里比较清晰地指出ThreadLocalMap
中的key
值为当前ThreadLocal
对象本身(具体来说是经过hash运算的值才是key值)
ThreadLocalMap
可以粗略地理解为是一个hashmap
,里面是Entry
数组,根据对象的hash值获取下标,把对应的value值存入数组中
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
存在内存泄露的问题
可以看到Entry
的在构造方法中用到了两个值,一个是ThreadLocal<?> k
作为key
值,一个是Object v
作为value
值,Entry
本身继承了WeakReference
,是一个弱引用,但是这个弱引用只针对key
值本身,并没有针对value
值。
解决方法,每次调用结束都要显式调用remove
方法
总结
为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则:
①ThreadLocal申明为private static final
Private与final 尽可能不让他人修改变更引用,
Static 表示为类属性,只有在程序结束才会被回收。
②ThreadLocal使用后务必调用remove方法。
最简单有效的方法是使用后将其移除。
引用拓展
类型 | 回收时间 | 应用场景 |
---|---|---|
强引用 | 一直存活,除非GC roots不可达 | 所有程序的场景,基本对象,自定义对象等 |
软引用 | 内存不足时会被回收 | 一般用在对内存非常敏感的资源上,如一些缓存上 |
弱引用 | 对象没有其他被强引用的话,只能存活到下一次GC前 | 生命周期很短的对象,例如ThreadLocal中的Key。 |
虚引用 | 随时会被回收, 创建了可能很快就会被回收 | 可能被JVM团队内部用来跟踪JVM的垃圾回收活动 |