ThreadLocalMap对象详解

Dcr 1年前 ⋅ 896 阅读

 

ThreadLocalMap对象是什么
本质上来讲,它就是一个Map,但是这个ThreadLocalMap与我们平时见到的Map有点不一样
    ×没有实现Map接口
    ×没有public的方法,最多有一个default的构造方法,因为这个ThreadLocalMap的方法仅仅在ThreadLocal类中调用,属于静态内部类
    ×ThreadLocalMap的Entry实现继承了WeakReference<ThreadLocal<?>>
    ×该方法仅仅用了一个Entry数组来存储Key,Value;Entry并不是链式形式,而是每个bucket里面仅仅放一个Entry
要了解ThreadLocalMap的实现,我们先从入口开始,就是往该Map中添加一个值:

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

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

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

先进行简单的分析,对该代码表层意思进行分析:
    ×首先看当前threadLocal在数组中的索引位置比如i=2对比该位置上面的Entry的key是否等于threadLocal的key,如果等于则将该Entry的value替换成最新的。
    ×若当前位置上面的Entry的key为空,说明ThreadLocal对象已经被回收了,则调用replaceStaleEntry
    ×若清理完无用条目(ThreadLocal被回收的条目)并且数组中的数据大小>阀值的时候对当前的Table进行重新哈系,所以该HashMap是处理冲突检测的机制是向后移位,清理过条目最终找到合适的位置;
了解完set方法,接下来看看get方法

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

先找到ThreadLocal的索引位置,如果索引位置处的entry不为空并且key与threadLocal是同一个对象,则直接返回,否则去后面的索引位置继续查找。
ThreadLocal造成内存泄漏问题
网上有这样一个例子:

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo {
    static class LocalVariable {
        private Long[] a = new Long[1024 * 1024];
    }

    // (1)
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
    // (2)
    final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>();

    public static void main(String[] args) throws InterruptedException {
        // (3)
        Thread.sleep(5000 * 4);
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    // (4)
                    localVariable.set(new LocalVariable());
                    // (5)
                    System.out.println("use local varaible" + localVariable.get());
                    localVariable.remove();
                }
            });
        }
        // (6)
        System.out.println("pool execute over");
    }
}

如果用线程池来操作ThreadLocal 对象确实会造成内存泄露, 因为对于线程池里面不会销毁的线程, 里面总会存在着<ThreadLocal, LocalVariable>的强引用, 因为final static 修饰的 ThreadLocal 并不会释放, 而ThreadLocalMap 对于 Key 虽然是弱引用, 但是强引用不会释放, 弱引用当然也会一直有值, 同时创建的LocalVariable对象也不会释放, 就造成了内存泄露; 如果LocalVariable对象不是一个大对象的话, 其实泄露的并不严重, 泄露的内存 = 核心线程数 * LocalVariable对象的大小; 所以, 为了避免出现内存泄露的情况, ThreadLocal提供了一个清除线程中对象的方法, 即 remove, 其实内部实现就是调用 ThreadLocalMap 的remove方法:

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

找到key对应的entry,并且清除entry的key(threadlocal)置空,随后清除过期的entry既可避免内存泄漏。
ThreadLocal应用场景:
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;  
}  

全部评论: 0

    我有话说: