ThreadLocal工作原理

什么是ThreadLocal,作用是什么

ThreadLocal 提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何时候访问这个本地变量的结果都是一致的。当此线程结束生命周期时,所有的线程本地实例都会被 GC。
作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
	This class provides thread-local variables.  These variables differ from
 their normal counterparts in that each thread that accesses one (via its
 {@code get} or {@code set} method) has its own, independently initialized
 copy of the variable.  {@code ThreadLocal} instances are typically private
  static fields in classes that wish to associate state with a thread (e.g.,
 a user ID or Transaction ID).
 	Each thread holds an implicit reference to its copy of a thread-local
 variable as long as the thread is alive and the {@code ThreadLocal}
 instance is accessible; after a thread goes away, all of its copies of
 thread-local instances are subject to garbage collection (unless other
 references to these copies exist).

深入解析ThreadLocal

先了解一下ThreadLocal类提供的几个方法:

/**
*用来获取当前线程在threadLocal中保存的当前线程的变量副本,如果变量没有当前线程,首先初始化为返回的值,通过调用setInitialValue()方法创建。
*/
public T get() { }
/**
*用来设置当前线程的变量副本。将当前线程变量的副本设置到threadLocal中
*/
public void set(T value) { }
/**
*用来移除当前线程在Threadlocal存储的变量副本
*/
public void remove() { }
/**
*一般是用来在使用时进行重写的,它是一个延迟加载方法。给当前线程初始化一个初始值
*/
protected T initialValue() { }

1,get()用来获取当前线程在threadLocal中保存的当前线程的变量副本

public T get() {
		//获取t当前线程
        Thread t = Thread.currentThread();
        //从当前线程中等到 ThreadLocalMap 
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        	//从map中获取entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            //不为空,读取当前的值并返回
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //map为空时,初始化为返回的值,通过调用setInitialValue()方法创建
        return setInitialValue();
    }
    

//查询Map中保存的数据
private Entry getEntry(ThreadLocal<?> key) {
    //1确定在散列数组中的位置,根据索引i获取entry
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //满足条件则返回该entry
    if (e != null && e.get() == key)
        return e;
    else
        //未查找到满足条件的entry,即存在hash冲突的情况下的处理
        return getEntryAfterMiss(key, i, e);
}
 
 
//解决hash冲突
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
        ThreadLocal<?> k = e.get();
        if (k == key)
            //找到和查询的key相同的entry则返回
            return e;
        if (k == null)
            //解决脏entry的问题
            expungeStaleEntry(i);
        else
            //继续向后环形查找
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;

通过get方法的getMap()发现,ThreadLocalMap 是ThreadLocal的底层重要实现,ThreadLocalMap 是 ThreadLocal 的静态内部类,当一个线程有多个 ThreadLocal 时,需要一个容器来管理多个 ThreadLocal,作用就是管理线程中多个 ThreadLocal。

ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
 }

 ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap

static class ThreadLocalMap {

        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        private static final int INITIAL_CAPACITY = 16;
        
        private Entry[] table;
        
        private int size = 0;
        
        private int threshold; // Default to 0

从源码中看到 ThreadLocalMap 其实就是一个简单的 Map 结构,底层是数组,有初始化大小,也有扩容阈值大小,数组的元素是 Entry,Entry继承与WeakReference,key 就是 ThreadLocal 的引用,value 是 ThreadLocal 的值。,用于解决 hash 冲突的方式采用的是线性探测法,如果发生冲突会继续寻找下一个空的位置。

ThreadLocal 内存泄漏

ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收。

那么如何避免内存泄漏呢?

在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收。

2,remove() 用来移除当前线程中变量的副本

	public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

remove 方法是先获取到当前线程的 ThreadLocalMap,并且调用了它的 remove 方法,从 map 中清理当前 ThreadLocal 对象关联的键值对,这样 value 就可以被 GC 回收了。

3,set()用来设置当前线程中变量的副本

public void set(T value) {
	// 获取当前线程
    Thread t = Thread.currentThread();
    // 获取ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    if (map != null) // 校验对象是否为空
        map.set(this, value); // 不为空set
    else
        createMap(t, value); // 为空创建一个map对象
}

	void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

主要流程为:
1,先获取当当前线程的引用;
2,利用这个引用来获取到 ThreadLocalMap;
3,如果 map 为空,则去创建一个 ThreadLocalMap;
4,如果 map 不为空,就利用 ThreadLocalMap 的 set 方法将 value 添加到 map 中。
其中 map 就是 ThreadLocalMap。调用 ThreadLocalMap.set() 时,会把当前 threadLocal 对象作为 key,想要保存的对象作为 value,存入 map。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值