swzhebei

常用链接

统计

最新评论

  • 1. re: 调用百度地图小实例
  • 如果我有100个经纬度 请问,您是不是再代码里写100个?你这样没有价值,如何获取动态的请说明!
  • --toly
  • 2. re: 调用百度地图小实例
  • 更改经纬度就不行了!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  • --你姥姥

ThreadLocal类(1)

1.目的

ThreadLocal目的是保存一些线程级别的全局变量,比如connection,或者事务上下文,避免这些值需要一直通过函数参数的方式一路传递。

2. 常见用法
举例其中一种常见用法:

public class Test2 {
	public static void main(String[] args) throws InterruptedException {
		testThreadLocal();
	}
 
	private static void testThreadLocal() {
		Util.setGlobalName("zili.dengzl");
		new Foo().printName();
	}
}
 
class Foo{
	public void printName(){
		System.out.println("globalName="+Util.getGlobalName());
	}
}
 
class Util {
	private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
 
	public static String getGlobalName() {
		return globalName.get();
	}
 
	public static void setGlobalName(String name) {
		globalName.set(name);
	}
}

3.实现分析

要实现上面这样的功能,最简单的想法是用一个Map<Thread,T>,如下:

class MockThreadLocal<T> {
	private Map<Thread, T> map = new HashMap<Thread, T>();
 
	public T get() {
		return (T) map.get(Thread.currentThread());
	}
 
	public void set(T value) {
		map.put(Thread.currentThread(), value);
	}
}

这样也能实现ThreadLocal的效果,但是有一个问题,当对应的线程消失后,map中对应的线程值并不会被回收,从而造成内存泄露。

事实上ThreadLocal是这样做的:

每个Thread都有一个threadLocalMap,key是threadLocal对象,value是具体使用的值。ThreadLocal对象的get就是先取得当前的Thread,然后从这个Thread的threadLcoalMap中取出值。set类似。

下面看下具体代码:

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return setInitialValue();
    }

注意这里如果取到没有该线程对应的值,会调用setInitialValue();,最终调用initialValue()生成一个值,这也是我们很多场景下要override这个方法的原因;

 

下面看一下getMap(Thread t)方法:

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

在Thread类中:

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

由此可见,所有的ThreadLocal的信息,最终是关联到Thread上的,线程消失后,对应的Thread对象也被回收,这时对应的ThreadLocal对象(该线程部分)也会被回收。

这里为什么是一个ThreadLocalMap呢,因为一个线程可以有多个ThreadLocal变量,通过map.getEntry(this)取得对应的某个具体的变量。

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

最后要注意的一点是,ThreadLocalMap的Entry是一个weakReference:

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

这里主要因为ThreadLocalMap的key是ThreadLocal对象,如果某个ThreadLocal对象所有的强引用没有了,会利用weakref的功能把他回收掉,然后复用这个entry。

考虑一下如果不用weakReference会出现什么情况:假设某个对象是这样引用的

private final ThreadLocal<String> globalName = new ThreadLocal<String>();

注意没有static,然后这个对象被不断的new出来,然后死掉,每次ThreadLocalmap中都会多出一个entry,然后这个entry强引用一个ThreadLocal对象,ThreadLocalMap本身就没有办法确定哪个entry是不用了的,如果恰好这个线程是线程池中的,会存活很久,那就杯具了。

ThreadLocalMap用了weakReference,失去强引用的ThreadLocal对象会在下次gc时被回收,然后ThreadLocalMap本身在get和set的时候会考察key为空的Entry,并复用它或者清除,从而避免内存泄露。

这样看来,HashMap也有一样的问题,但为什么hashMap不这样呢,因为hashMap的put是业务代码操作的,因此如果有长期存活的HashMap,(比如static的)业务代码put进去就有义务去remove,但ThreadLocal的put操作时ThreadLocal类干的,业务代码不知道,因此也不会去做remove,而ThreadLocalMap本身不知道引用他的某个entry的key的对象什么时候死掉了,那么如果不用弱引用,就不知道这个ThreadLocal对象什么时候需要回收了。

附:

这里补充一下weakReference的用法供参考(当强引用不存在时,下次垃圾回收会回收弱引用所引用的对象):

		Object o = new Object();
		WeakReference<Object> ref = new WeakReference<Object>(o);
		System.out.println(ref.get());
		o=null;
		System.gc();
		System.out.println(ref.get());

结果输出:

 java.lang.Object@de6ced
 null

4. FAQ

4.1 为什么一般的ThreadLocal用法都要加static,如下:

class Test {
    private static final ThreadLocal<String> globalName = new ThreadLocal<String>();
}
answer:事实上,不一定是要static,但使用它的对象在业务需要范围类一定要是单例。因为根据前面的分析,ThreadLocalMap是以ThreadLocal对象为key的,如果Test类不是static,也不是单例的,那么两个Test对象就有两个key,取出来的数据肯定不同

 

class TestThreadLocal{    
    public static void main(String[] args) {
		Test t1 = new Test();
		Test t2 = new Test();
 
		t1.pool.set("a");
		System.out.println(t1.pool.get());
		System.out.println(t2.pool.get());
	}
}
class Test{
	public ThreadLocal pool = new ThreadLocal();
}

输出将会是:a,null

原因就无需多解释了。唯一需要啰嗦的一点是,就算一般情况都是单例,上面那个weakreference还是必要的,因为作为框架代码,不能保证正常使用的情况下一个线程有很多ThreadLocal,如果不用weakreference,就会有内存泄漏的风险,特别是针对线程池中的线程。

posted on 2012-05-31 13:15 透明的鱼 阅读(585) 评论(0)  编辑  收藏


只有注册用户登录后才能发表评论。


网站导航: