也谈ThreadLocal

 欢迎赐教博客地址(http://www.cnblogs.com/shizhongtao/p/5358411.html)

对于ThreadLocal使用,网上一堆一堆的。什么内存泄露,什么线程不安全。这里记下自己对其的理解。以备不时之需。

关于ThreadLocal

ThreadLocal类,在jdk中这样描述:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own,
independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

 

其实说白了就是保持本线程变量在不同阶段的共享,用我曾看到的一句话描述就是:ThreadLocal模式主要用于解决同一线程在不同开发层次中数据共享问题,
而不是用于不同开发层次数据传递问题,比如spring的数据库连接传递性的实现。我的理解就是,threadlocal里面的数据本身就是每个线程一份,也就是线程私有的,
没有用它来做线程共享的概念(不对的话,请赐教)。
另外,基于上面一点,既然他是一个线程共享的私有变量,并且为了避免引起不必要的问题,你可以把他定义为,final 并且 static的变量
final static ThreadLocal<T> local;
另外,关于threadLocal使用问题,我觉得一般是不正确的编码引起的。比如:
  •  为了方便测试,我定义一个ThreadLocal类
    public static class ThreadMyLocal<T> extends ThreadLocal<T> {
    
            @Override
            protected void finalize() throws Throwable {
                System.out.println("threadLocal  gc:");
                super.finalize();
            }
    
        }

     

  •  定义处理类,引入全局的ThreadLocal对象
    public static class Handler{
            static ThreadMyLocal<Person> local;
    
            private Person getPerson() {
                if (local == null) {
                    local = new ThreadMyLocal<>();
                    }
                if(local.get()==null){
                    Person person = new Person();
                    person.setAge(100);
                    local.set(person);
                     }
                return local.get();
            }
            public void printSome(){
    
                Person person = getPerson();
                System.out.println(person.getAge()+":Person对象age属性");
            }
    
            @Override
            protected void finalize() throws Throwable {
               System.out.println("Handler GC");
                super.finalize();
            }
    
        }

     

  • 测试用的变量 person对象(ThreadLocal存放的值)
    public static class Person {
    
            private int age;
    
            public int getAge() {
                return age;
            }
            public void setAge(int age) {
                this.age = age;
            }
    
            @Override
            protected void finalize() {
                System.out.println("Person  gc");
            }
    
        }

     

测试1,我们进行第一次测试

 

@Test
    public void readExcelTest() throws InterruptedException {

        Handler h=new Handler();
        h.printSome();
        h=null;
        Thread.sleep(1000);
        System.out.println("gc1");
        System.gc();

        Handler h2=new Handler();
        h2.printSome();

        Thread.sleep(1000);
        System.out.println("gc2");
        System.gc();
    }

打印结果是:

100:Person对象age属性
gc1
100:Person对象age属性
Handler GC
gc2

我们从打印结果看,只能看到handler被销毁回收,原因很简单,因为这里面并没有启动新的线程,而是在主线程中来操作Threadlocal对象,他们共用一个Pserson对象,所以Person不该被销毁。

测试2 第二次测试

@Test
    public void readExcelTest() throws InterruptedException {

        Handler h=new Handler();
        h.printSome();
        //注意此处
        h.local=null;
        Thread.sleep(1000);
        System.out.println("gc1");
        System.gc();
        h=new Handler();
        h.printSome();

        Thread.sleep(1000);
        System.out.println("gc2");
        System.gc();

    }

打印结果是:

100:Person对象age属性
gc1
100:Person对象age属性
threadLocal  gc:
gc2
Handler GC

可以发现,设置   h.local=null;时候。ThreadLocal对象在设置为null时候,被回收了,而Person对象并没有被回收。当把threadlocal实例( h.local=null;)以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收.

但是,其中的value却不能回收,因为存在一条从current thread连接过来的强引用. 只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收

这里推荐一篇博客(深入JDK源码之ThreadLocal类),它里面有这样一段介绍:

在java api中:ThreadLocal有这样的观点(转载)
ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。
ThreadLocal 在类中通常定义为静态类变量。
每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。
对ThreadLocal的修改,其实是一种对线程资源的修改,可以说这是一种空间换取时间的设计。
ThreadLocal内存泄漏

很多人认为:threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没 有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用threadlocal的remove方法。
说的也比较正确,当value不再使用的时候,调用remove的确是很好的做法.但内存泄露一说却不正确. 这是threadlocal的设计的不得已而为之的问题. 首先,让我们看看在threadlocal的生命周期中,都存在哪些引用吧.
看下图: 实线代表强引用,虚线代表弱引用。 每个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每个key都弱引用指向threadlocal.
当把threadlocal实例tl置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收. 但是,我们的value却不能回收,因为存在一条从current thread连接过来的强引用.
只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收。通过源码看下此处的实现,如下: public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); // 将当前threadLocal实例作为key else createMap(t, value); } 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); // 构造key-value实例 int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); } static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); // 构造key弱引用 value = v; } } 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } 从中可以看出,弱引用只存在于key上,所以key会被回收. 而value还存在着强引用.只有thead退出以后,value的强引用链条才会断掉。一旦某个ThreadLocal对象没有强引用了,
它在所有线程 内部的ThreadLocalMap中的key都将被GC掉(此时value还未回收),在map后续的get/set中会探测到key被回收的 entry,将其 value 设置为 null 以帮助
GC,因此 value 在 key 被 GC 后可能还会存活一段时间,但最终也会被回收。这个过程和java.util.WeakHashMap的实现几乎是一样的。 因此ThreadLocal本身是没有内存泄露问题的,通常由它引发的内存泄露问题都是线程只 put 而忘了 remove 导致的,从上面分析可知,即使线程退出了,只要 ThreadLocal
还有强引用,该线程曾经 put 过的东西是不会被回收掉的。

 上面说的比较清楚了,如果你想value值被回收,只有当当前线程退出。

测试4,为了说明以上内容

@Test
    public void readExcelTest2() throws InterruptedException {

         new Thread(new Runnable() {

            @Override
            public void run() {
                Handler h=new Handler();
                h.printSome();
                //h.local=null;
            }
        });
        System.out.println("gc1");
        System.gc();
        Thread.sleep(3000);
        System.out.println("gc2");
        System.gc();
        
    }

打印结果是:

gc1
100:Person对象age属性
gc2
Person  gc
Handler GC

 


 从上面结果可以看出,线程退出Persong确实被回收了。

ThreadLocal之Web服务器

当你在javaEE项目中使用ThreadLocal时候就可能会出现内存问题,我们知道不管是tomcat还是webLogic本身都有一个线程池的概念,这样可以提高服务器的性能。简单来说就是当一个请求过来,容器就会去线程池中拿走一个线程去使用;请求
结束就会把这个线程放到线程池中等待使用。正是这个原因,使用的”ThreadLocal”保存的值,在下一次请求依然存在,而且值也不能完全确定(也可能两次同一个线程,也可能不是),这是其中一个问题;另外假如Threadlocal保存的值有5M大小,
在生产环境,我们把服务器线程池设置允许150或者更多,这样只是Threadlocal就会占用去差不多1G的内存空间,也可能就会出现OutOfMemoryError这样的错误。
 
正常情况下使用ThreadLocal不会造成内存溢出,在程序中也不要滥用Threadlocal,能用参数传递的就不要使用Threadlocal,还是前面那句话:ThreadLocal模式主要用于解决同一线程在不同开发层次中数据共享问题,而不是用于不同开发层次数据传递问题。

 

posted @ 2016-04-06 11:09  bingyulei  阅读(488)  评论(0编辑  收藏  举报