深入分析ThreadLocal

转载请注明出处,团队缺人,有意者私信哈

ThreadLocal 源码解读

基本概念

当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭Thread Confinement。当某个对象封闭在一个线程中时,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的。

线程封闭其中一种实现方式就是ThreadLocal的方式,简单说,就是对于一个共享变量为每个线程都保存了一个副本,ThreadLocal为每个使用该变量的线程提供独立的变量副本,使之成为独立的线程局部变量。

ThreadLocal模式解决的是同一线程中隶属于不同开发层次的数据共享问题,而不是在不同的开发层次中进行数据传递。 使用ThreadLocal模式,可以使得数据在不同的编程层次得到有效地共享。

从上面图中我们可以看到,由于ThreadLocal所操作的是维持于整个Thread生命周期的副本(ThreadLocalMap),所以无论在J2EE程序程序的哪个层次(表示层、业务逻辑层或者持久层),只要在一个Thread的生命周期之内,存储于ThreadLocalMap中的对象都是线程安全的(因为ThreadLocalMap本身仅仅隶属于当前的执行线程,是执行线程内部的一个属性变量。我们用图中的阴影部分来表示这个变量的存储空间)。而这一点,正是被我们用于来解决多线程环境中的变量共享问题的核心技术。ThreadLocal的这一特性也使其能够被广泛地应用于J2EE开发中的许多业务场景。
摘自:http://wely.iteye.com/blog/2295284

ThreadLocal使用是比较直观的,本次的重点也不在于使用,而在内部的实现方式。

实现源码

Thread类内部维护了ThreadLocalMap结构,但是ThreadLocalMap的维护交给了ThreadLocal对象

public class Thread{
 	// 由于使用了内部类,Thread对象有threadLocals引用,但是操作权在ThreadLocal对象手中
	ThreadLocal.ThreadLocalMap threadLocals = null;
}
//ThreadLocalMap是定义在Thread中,内部维护了一个Entry数组
  /**
 * ThreadLocalMap is a customized hash map suitable only for
 * maintaining thread local values. No operations are exported
 * outside of the ThreadLocal class. The class is package private to
 * allow declaration of fields in class Thread.  To help deal with
 * very large and long-lived usages, the hash table entries use
 * WeakReferences for keys. However, since reference queues are not
 * used, stale entries are guaranteed to be removed only when
 * the table starts running out of space.
 */
static class ThreadLocalMap {
	//每个ThreadLocal对象都维护了一个threadLocalHashCode,根据这个值和ThreadLocalMap的容量进行交运算来确定下标
    private Entry[] table;

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
}
//实际上Entry是对ThrealLocal的封装
/**
 * The entries in this hash map extend WeakReference, using
 * its main ref field as the key (which is always a
 * ThreadLocal object).  Note that null keys (i.e. entry.get()
 * == null) mean that the key is no longer referenced, so the
 * entry can be expunged from table.  Such entries are referred to
 * as "stale entries" in the code that follows.
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

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

内存相关

Entry是对ThreadLocal的简单封装,继承了WeakReference,使用WeakReference的好处是,reference实例不会影响到被应用对象的GC回收行为(即只要对象被WeakReference对象之外所有的对象解除引用后,该对象可以被GC回收)只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。

以下面的代码为例来说明内存的相关情况:

public class ThreadLocalDemo {
    @Test
    public void test() throws InterruptedException{
        Demo demo = new Demo();
        ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>(){
            @Override
            protected void finalize() throws Throwable {
                super.finalize();
                System.out.println("threadLocal finalize");
            }
        };
        threadLocal.set(demo);
        demo = null;                 //1
	//  threadLocal.set(null);       //2  
	//  threadLocal.remove();        //4
		threadLocal = null;           //3
        System.gc();
        
    }
    class Demo{
        byte data[] = null;
        public Demo() {
            data ="abcd".getBytes();
        }
        @Override
        protected void finalize() throws Throwable {
            System.out.println("demo finalize");
            super.finalize();
        }
    }
}

示例代码运行后,只会输出threadLocal finalize,和直觉不太一致,因为在程序1处设置了目标对象为null,期望结果是GC的时候回收掉这块内存,但是demo finalize并没有输出,表明它并没有被回收。简单画了一下内存图(不要在意细节,就是个示例)

可以看到,demo=null,而demo的示例对象仍然有Entry中的value的强引用,将代码稍作修改也可以看出这一点

@Test
public void test() throws InterruptedException{
    Demo demo = new Demo();
    ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>(){
        @Override
        protected void finalize() throws Throwable {
            super.finalize();
            System.out.println("threadLocal finalize");
        }
    };
    threadLocal.set(demo);
    demo = null;
    System.out.println(new String(threadLocal.get().data));

    System.gc();
}

输出结果为abcd,也印证了上面的观点。事实上我们希望回收了这块内存,但是显然没有达到预期,这可能会导致内存泄露。

WeakReference的使用

回过头来再看第一段代码,如果按照分析demo=null的方式来说明,那么在代码中设置了threadlocal =null,而Entry的key同样指向了这块内存,照理说也应该不会被回收,但是第一段代码输出了结果threadLocal finalize,说明ThreadLocal对象被回收了。这个的原因就是WeakReference的使用

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

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

由于设置了Entry对象中ThreadLocal是WeakReference的,上面也提到过不会影响到对象的GC操作,因此当设置threadlocal=null的时候,对应的内存可以被GC回收掉

内存泄露的解决方式

如果想要让GC回收demo实例对应的内存区域,那么应该怎么办呢?有两种方式,放开第一段代码注释掉的2或者4处就可以了。分析一下原因:

//threadLocal.set(null);
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

结合内存图来看,直接断开了Entry的value的指向,所以在下次gc的时候,demo内存没有了强引用,所以直接被GC了,也就执行了finalize()方法

//threadLocal.remove();
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;
        }
    }
}

remove方法更彻底,直接把Entry对象设置为null,下次GC自然会回收相应内存

总结一下

The major issue with ThreadLocal is the potential risk of memory leak. Indeed, the ThreadLocalMap contains an array of Entry whose key value extends WeakReference so one can naively think that it will be garbaged by GC automatically when memory is low. But the fact is that only the key (ThreadLocal object) is a weak reference, not the target object itself !

上面基本讨论了一下ThreadLocal的实现,也指出了ThreadLocal使用中可能造成内存泄露的原因。

posted @ 2016-10-23 14:09  卡卡西sir  阅读(589)  评论(0编辑  收藏  举报