ThreadLocal使用,应用场景,源码实现,内存泄漏

首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,通常状况下,经过ThreadLocal.set() 到线程中的对象是该线程本身使用的对象,其余线程是不须要访问的,也访问不到的。各个线程中访问的是不一样的对象。 

另外,说ThreadLocal使得各线程可以保持各自独立的一个对象,并非经过ThreadLocal.set()来实现的,而是经过每一个线程中的new 对象 的操做来建立的对象,每一个线程建立一个,不是什么对象的拷贝或副本。经过ThreadLocal.set()将这个新建立的对象的引用保存到各线程的本身的一个map中,每一个线程都有这样一个map,执行ThreadLocal.get()时,各线程从本身的map中取出放进去的对象,所以取出来的是各自本身线程中的对象,ThreadLocal实例是做为map的key来使用的。 

若是ThreadLocal.set()进去的东西原本就是多个线程共享的同一个对象,那么多个线程的ThreadLocal.get()取得的仍是这个共享对象自己,仍是有并发访问问题。 

下面来看一个hibernate中典型的ThreadLocal的应用: 算法

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

能够看到在getSession()方法中,首先判断当前线程中有没有放进去session,若是尚未,那么经过sessionFactory().openSession()来建立一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的惟一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession做为这个值的key,要取得这个session能够经过threadSession.get()来获得,里面执行的操做实际是先取得当前线程中的ThreadLocalMap,而后将threadSession做为key将对应的值取出。这个session至关于线程的私有变量,而不是public的。 session

试想若是不用ThreadLocal怎么来实现呢?可能就要在action中建立session,而后把session一个个传到service和dao中,这可够麻烦的。或者能够本身定义一个静态的map,将当前thread做为key,建立的session做为值,put到map中,应该也行,这也是通常人的想法,但事实上,ThreadLocal的实现恰好相反,它是在每一个线程中有一个map,而将ThreadLocal实例做为key,这样每一个map中的项数不多,并且当线程销毁时相应的东西也一块儿销毁了,不知道除了这些还有什么其余的好处。 多线程

 

总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。概括了两点: 
1。每一个线程中都有一个本身的ThreadLocalMap类对象,能够将线程本身的对象保持到其中,各管各的,线程能够正确的访问到本身的对象。 
2。将一个共用的ThreadLocal静态实例做为key,将不一样对象的引用保存到不一样线程的ThreadLocalMap中,而后在线程执行的各处经过这个静态ThreadLocal实例的get()方法取得本身线程保存的那个对象,避免了将这个对象做为参数传递的麻烦。并发

 

固然若是要把原本线程共享的对象经过ThreadLocal.set()放到线程中也能够,能够实现避免参数传递的访问方式,可是要注意get()到的是那同一个共享对象,并发访问问题要靠其余手段来解决。但通常来讲线程共享的对象经过设置为某类的静态变量就能够实现方便的访问了,彷佛不必放到线程中。 

ThreadLocal的应用场合,我以为最适合的是按线程多实例(每一个线程对应一个实例)的对象的访问,而且这个对象不少地方都要用到。 this

 

固然ThreadLocal并不能替代同步机制,二者面向的问题领域不一样。同步机制是为了同步多个线程对相同资源的并发访问,是为了多个线程之间进行通讯的有效方式;而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样固然不须要对多个线程进行同步了。因此,若是你须要进行多个线程之间进行通讯,则使用同步机制;若是须要隔离多个线程之间的共享冲突,可使用ThreadLocal,这将极大地简化咱们的程序,使程序更加易读、简洁。ThreadLocal类为各线程提供了存放局部变量的场所。 spa

 

JDK中ThreadLocal的实现:hibernate

并不是在ThreadLocal中有一个Map,而是在每一个Thread中存在这样一个Map,具体是ThreadLocal.ThreadLocalMap。当用set时候,往当前线程里面的Map里 put 的key是当前的ThreadLocal对象。而不是把当前Thread做为Key值put到ThreadLocal中的Map里。 线程

public class ThreadLocal<T> {
    private final int threadLocalHashCode = nextHashCode();
    private static int nextHashCode = 0;
    private static final int HASH_INCREMENT = 0x61c88647;
private static synchronized int nextHashCode() { int h = nextHashCode; nextHashCode = h + HASH_INCREMENT; return h; }
public ThreadLocal() { } public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) return (T)map.get(this); // Maps are constructed lazily. if the map for this thread // doesn't exist, create it, with this ThreadLocal and its // initial value as its only entry. T value = initialValue(); createMap(t, value); return value; } public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } .......
static class ThreadLocalMap {   ........ } }

 

 

 

ThreadLocal内存泄漏:code

每一个Thread实例都具有一个ThreadLocal的map,以ThreadLocal Instance为key,以绑定的Object为Value。而这个map不是普通的map,它是在ThreadLocal中定义的,它和普通map的最大区别就是它的Entry是针对ThreadLocal弱引用的,即当外部ThreadLocal引用为空时,map就能够把ThreadLocal交给GC回收,从而获得一个null的key。 

这个threadlocal内部的map在Thread实例内部维护了ThreadLocal Instance和bind value之间的关系,这个map有threshold,当超过threshold时,map会首先检查内部的ThreadLocal(前文说过,map是弱引用能够释放)是否为null,若是存在null,那么释放引用给gc,这样保留了位置给新的线程。若是不存在slate threadlocal,那么double threshold。除此以外,还有两个机会释放掉已经废弃的threadlocal占用的内存,一是当hash算法获得的table index恰好是一个null key的threadlocal时,直接用新的threadlocal替换掉已经废弃的。另外每次在map中新建一个entry时(即没有和用过的或未清理的entry命中时),会调用cleanSomeSlots来遍历清理空间。此外,当Thread自己销毁时,这个map也必定被销毁了(map在Thread以内),这样内部全部绑定到该线程的ThreadLocal的Object Value由于没有引用继续保持,因此被销毁。 

从上能够看出Java已经充分考虑了时间和空间的权衡,可是由于置为null的threadlocal对应的Object Value没法及时回收。map只有到达threshold时或添加entry时才作检查,不似gc是定时检查,不过咱们能够手工轮询检查,显式调用map的remove方法,及时的清理废弃的threadlocal内存。须要说明的是,只要不往不用的threadlocal中放入大量数据,问题不大,毕竟还有回收的机制。 

综上,废弃threadlocal占用的内存会在3中状况下清理: 
1 thread结束,那么与之相关的threadlocal value会被清理 
2 GC后,thread.threadlocals(map) threshold超过最大值时,会清理 
3 GC后,thread.threadlocals(map) 添加新的Entry时,hash算法没有命中既有Entry时,会清理 
那么什么时候会“内存泄露”?当Thread长时间不结束,存在大量废弃的ThreadLocal,而又再也不添加新的ThreadLocal(或新添加的ThreadLocal刚好和一个废弃ThreadLocal在map中命中)时。对象

相关文章
相关标签/搜索