ThreadLocal相信大家都用过,但你知道他的原理吗,今天了不起带大家学习ThreadLocal。
在多线程编程中,经常会遇到需要在不同线程中共享数据的情况。通常情况下,为了保证线程安全,我们需要使用锁或其他同步机制。然而,有些情况下,我们希望在每个线程中都有一份独立的数据副本,这就是ThreadLocal派上用场的地方。
ThreadLocal翻译过来就是线程本地,也就是本地线程变量,意思是ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。ThreadLocal提供了一种机制,允许我们为每个线程创建独立的变量,每个线程都可以独立访问自己的变量,而不会干扰其他线程的数据。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量,各个线程间互不影响,从而实现线程安全。
ThreadLocal的原理涉及到两个重要概念:ThreadLocal实例和ThreadLocalMap。
每个ThreadLocal对象实际上是一个容器,用于存储线程本地的变量副本。每个线程都可以拥有自己的ThreadLocal实例,这些实例可以存储不同的数据,互相之间互不影响。
ThreadLocalMap是ThreadLocal的底层数据结构,它是一个哈希表。每个线程都有一个与之相关联的ThreadLocalMap,用于存储该线程所拥有的ThreadLocal实例以及对应的值。ThreadLocalMap中的键是ThreadLocal实例,值是该线程对应ThreadLocal实例的变量副本。
当我们调用ThreadLocal的set()
方法设置值时,实际上是在当前线程的ThreadLocalMap中以ThreadLocal实例为键,将值存储在对应的位置。而调用get()
方法时,则是从当前线程的ThreadLocalMap中根据ThreadLocal实例获取对应的值。
public static void main(String[] args) {
IntStream.range(1, 5).forEach(i - > new Thread(() - > {
// 设置线程中本地变量的值
threadLocal.set("thread-" + i);
// 打印当前线程中本地内存中本地变量的值
System.out.println(threadLocal.get());
// 清除本地内存中的本地变量
threadLocal.remove();
// 打印本地变量
System.out.println("thread-" + i + " after remove: " + threadLocal.get());
}).start());
}
/*
thread-1
thread-4
thread-4 after remove: null
thread-2
thread-3
thread-2 after remove: null
thread-1 after remove: null
thread-3 after remove: null
*/
从结果可以看到,每一个线程都有各自的值,并且互不影响。
先看一下 ThreadLocal 和 Thread 的关系
Thread类中有一个threadLocals属性,是ThreadLocal内部类ThreadLocalMap类型的变量,ThreadLocalMap可以看作是一个HashMap,其内部有一个内部类为 Entry,继承了WeakReference
,是一个弱引用。Entry的key是ThreadLocal
,value是Object类型的值。
大致了解了Thread和ThreadLocal的关系之后,看一下Thread Local的源码:我们只要看其主要的几个方法,就可以完全了解ThreadLocal的原理了。
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null)
// map不为空,则直接赋值
map.set(this, value);
else
// map为空,则创建一个ThreadLocalMap对象
createMap(t, value);
}
// 根据提供的线程对象,和指定的值,创建一个ThreadLocalMap对象
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
// threadLocals是Thread类的一个属性
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/*
Thread 类 182行
// ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class.
与该线程有关的ThreadLocal值。这个映射由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;
*/
// ThreadLocalMap中的内部类,存放key,value
static class Entry extends WeakReference< ThreadLocal< ? > > {
// 与此ThreadLocal关联的值
Object value;
// k:ThreadLocal的引用,被传递给WeakReference的构造方法
Entry(ThreadLocal< ? > k, Object v) {
super(k);
value = v;
}
}
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 通过当前线程获取线程中的ThreadLocal.ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
// map不为空,通过this(当前对象,即ThreadLocal对象)获取Entry对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
// Entry不为空,则直接返回Entry中的value值
return result;
}
}
// 如果map或Entry为空,则返回初始值-null
return setInitialValue();
}
// 设置初始值,初始化ThreadLocalMap对象,并设置value为 null
private T setInitialValue() {
// 初始化值,此方法返回 null
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.ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
// 移除对象
m.remove(this);
}
// 根据key,删除对应的所有值
private void remove(ThreadLocal< ? > key) {
Entry[] tab = table;
int len = tab.length;
// 获取key对应的 Entry[] 下标
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
// 获取下一个Entry对象
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
// 通过重新哈希位于staleSlot和下一个null插槽之间的任何可能冲突的条目,来清除陈旧的条目。这还会清除尾随null之前遇到的所有其他过时的条目,防止出现内存泄漏问题
expungeStaleEntry(i);
return;
}
}
}
总结:
虽然ThreadLocal在某些情况下非常有用,但过度使用它也可能导致内存泄漏问题。因为ThreadLocalMap中的数据只有在线程结束时才会被释放,如果没有正确地清理ThreadLocal实例,就可能会导致无限制的数据积累。
另外,ThreadLocal不适合在并发量非常大的情况下使用,因为每个线程都会创建自己的变量副本,可能会导致内存消耗过大。
在上面的代码中,我们可以看出,当前ThreadLocal的引用k被传递给WeakReference的构造函数,所以ThreadLocalMap中的key为ThreadLocal的弱引用。
如果当前线程一直存在且没有调用该ThreadLocal的remove方法,如果这个时候别的地方还有对ThreadLocal的引用,那么当前线程中的ThreadLocalMap中会存在对ThreadLocal变量的引用和value对象的引用,是不会释放的,会造成内存泄漏。
ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的value值不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,也会造成内存泄漏。
ThreadLocal是一种多线程编程的工具,可以帮助我们在多线程环境中管理线程本地的变量。它通过ThreadLocal实例和ThreadLocalMap的组合实现了这一功能。
使用ThreadLocal时需要注意内存泄漏和性能问题,确保合理使用。
使用完ThreadLocal后,一定执行remove操作,避免出现内存泄漏情况。
全部0条评论
快来发表一下你的评论吧 !