ThreadLocal 原理分析

ThreadLocal 实现原理

ThreadLocal的实现是这样的:每个Thread 维护一个 ThreadLocalMap 映射表,这个映射表的 key 是 ThreadLocal 实例本身,value 是真正需要存储的 Object。

也就是说 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。值得注意的是, ThreadLocalMap 是使用 ThreadLocal 的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。

ThreadLocal的源码分析

ThreadLocalMap
ThreadLocalMap与Thread的关系

在ThreadLocal中,用来存储线程的局部变量的

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

t.threadLocals实际上就是访问Thread类中的ThreadLocalMap这个成员变量

public class Thread implements Runnable{

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
}

从代码可知,每一个线程都有自己单独的ThreadLocalMap实例,而对应这个线程的所有本地变量都会保存到这个map内。

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

  • Thread t 是通过Thread.currentThread()来获取的表示当前线程,然后对当前线程中的threadLocals进行初始化。

  • this 表示当前对象的引用,也就是ThreadLocal声明的对象。那么多个线程对应同一个ThreadLocal实例,怎么对每一个ThreadLocal对象做区分呢?

ThreadLocalMap的存储结构

ThreadLocalMap是一个静态内部类,内部定义了一个Entry对象用来真正存储数据。

static class ThreadLocalMap {

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }

   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

        //构造一个Entry数组,并设置初始大小
        table = new Entry[INITIAL_CAPACITY];
        //计算Entry数据下标
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        //将`firstValue`存入到指定的table下标中
        table[i] = new Entry(firstKey, firstValue);
        //设置节点长度为1
        size = 1;
        //设置扩容的阈值
        setThreshold(INITIAL_CAPACITY);
    }

}

Entry继承 WeakReference带来的好处与坏处?

get

从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,返回获取的value。如果map为null,进行初始化。

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;
}


set

设置当前线程的线程局部变量的值

 public void set(T value) {
    //获取当前执行的线程
    Thread t = Thread.currentThread();
    //根据当前线程得到当前线程的ThreadLocalMap对象
    ThreadLocalMap map = getMap(t);
    //map不为空,当前线程已经构造过ThreadLocalMap,直接将值存储到map中
    if (map != null)
        map.set(this, value);
    else
       //map为空,调用 createMap
        createMap(t, value);
}

如果map不为空,说明当前线程已经有了一个ThreadLocalMap实例,直接将当前value设置到ThreadLocalMap中

private void set(ThreadLocal<?> key, Object value) {

    // We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);
    //从i开始往后一直遍历到数组最后一个Entry
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //如果key相等,覆盖
        if (k == key) {
            e.value = value;
            return;
        }
 
        //如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    //扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


  • map中的key为空,会清理key=null的数据。Entry的key为弱引用,如果key为空,说明ThreadLocal这个对象被GC回收了。replaceStaleEntry方法进行清除。
  • 扩容过程会清除无效的Entry。cleanSomeSlots方法进行清除。
remove

从Entry[]中删除指定的key。


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

private void remove(ThreadLocal<?> key) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            if (e.get() == key) {
                e.clear();
                expungeStaleEntry(i);
                return;
            }
        }
    }

ThreadLocal的内存泄漏

内存泄漏原因

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现keynullEntry,就没有办法访问这些keynullEntryvalue,如果当前线程再迟迟不结束的话,这些keynullEntryvalue就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。

其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocalget(),set(),remove()的时候都会清除线程ThreadLocalMap里所有keynullvalue

建议的使用方法是:

  • 将ThreadLocal变量定义成private static的。

    这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露

  • 每次使用完ThreadLocal,都调用它的remove()方法,清除数据。

为什么使用弱引用

从表面上看内存泄漏的根源在于使用了弱引用。网上的文章大多着重分析ThreadLocal使用了弱引用会导致内存泄漏,但是另一个问题也同样值得思考:为什么使用弱引用而不是强引用?

我们先来看看官方文档的说法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了应对非常大和长时间的用途,哈希表使用弱引用的 key。

下面我们分两种情况讨论:

  • key 使用强引用:

    引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

  • key 使用弱引用:

    引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

    比较两种情况,我们可以发现:

    由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

    ThreadLocal内存泄漏的根源是:

由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。

场景

常见的ThreadLocal使用场景为 用来解决数据库连接、Session管理等。

数据库连接
 
 private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {  
    public Connection initialValue() {  
        return DriverManager.getConnection(DB_URL);  
    }  
};  
  
public static Connection getConnection() {  
    return connectionHolder.get();  
} 

Session管理
private static final ThreadLocal threadSession = new ThreadLocal();  
  
public static Session getSession() throws InfrastructureException {  
    Session s = (Session) threadSession.get();  
    try {  
        if (s == null) {  
            s = getSessionFactory().openSession();  
            threadSession.set(s);  
        }  
    } catch (HibernateException ex) {  
        throw new InfrastructureException(ex);  
    }  
    return s;  
} 

传递变量
public class BizDataContext {

   public static final String KEY_NAMESPACE = BizDataContext.class.getName();


   private static ThreadLocal<BizDataContext> bizDataContext;
   private Map<String, Object> store = new HashMap();

   static {
       bizDataContext = new ThreadLocal(){
           //初始化自定义上下文数据
           protected BizDataContext initialValue(){
               return new BizDataContext();
           }
       };
   }

   private static BizDataContext getBizDataConetxt(){
       return (BizDataContext) bizDataContext.get();
   }

   private static Map<String, Object> getStore(){
       return getBizDataConetxt().store;
   }

   /***
    * 对数据存储map store的操作
    * @param key
    * @return
    */
   private static Object get(String key){
       return getStore().get(KEY_NAMESPACE+key);
   }

   private static Object set(String key, Object value) {
       return getStore().put(KEY_NAMESPACE+key, value);
   }

   public static Object remove(String key){
       return getStore().remove(KEY_NAMESPACE+key);
   }

   public static void clear(){
       getStore().clear();
   }

   /***
    * 存储value
    * @param key
    * @param value
    */
   public static void setValue(String key,String value){
       set(key, value);
   }

   public static String getValue(String key){
       return  (String)get(key);
   }


}


参考:深入分析 ThreadLocal 内存泄漏问题

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值