zoukankan      html  css  js  c++  java
  • ThreadLocal的实现原理(读书笔记)

    ThreadLocal的set方法和get方法,从set方法开始:
    public void set(T value) {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取线程的局部变量
        if (map != null)//判断map是否存在
            map.set(this, value);//set值 key是当前ThreadLocal对象 value是value
        else
            createMap(t, value);//否则 创建一个map设置值
    }
         get方法:
    public T get() {
        Thread t = Thread.currentThread();//获取当前线程
        ThreadLocalMap map = getMap(t);//获取线程的局部变量map
        if (map != null) {//当map存在时
            ThreadLocalMap.Entry e = map.getEntry(this);//获取entry(键值对)
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;//返回值
            }
        }
        return setInitialValue();//返回null
    }
         在了解了ThreadLocal的内部实现后,我看到一个问题,那就是这些变量是维护在Thread类内部的,这也意味着只要线程不退出,对象的引用将一直存在,
         当线程退出是,Thread类会进行一些清理工作,其中就包括清理ThreadLocalMap.下面是具体实现:在Thread类内部:
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }
         因此,如果我们使用线程池,那就意味着当前线程未必会退出,(比如固定大小的线程池,线程总是存在的,) 如果这样,将一些大大的对象设置到ThreadLocal中,可能会使系统出现内存在泄漏,
         此时,你希望及时的GC,最好使用ThreadLocal.remove()方法将这个变量移除,就像我们习惯性的关闭数据库链接一样,如果你确定不需要这个对象了,那么就应该告诉虚拟机,把他回收掉,防止内存泄漏,
         另外一种有趣的情况是JDK也可能允许你想释放普通变量一样释放ThreadLocal.比如,我么你有时候为了加入GC.会特意写出类似obj=null之类的代码.如果这么做,obj所指向的对象就会更容易地垃圾回收器发现,从而加速回收,
         同理,如果对于ThreadLocal的变量,我们也手动将其设置null.比如t1=null.那么这个ThreadLocal对应的所有线程的局部变量都有可能被回收,我们写个小例子来看一看奥秘:
    public class ThreadLocalDemo_Gc {
        static volatile ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<SimpleDateFormat>() {
            @Override
            protected void finalize() throws Throwable { //重载了finalize() 当对象在GC时,打印信息
                System.out.println(this.toString() + " is gc");
            }
        };
    
        static volatile CountDownLatch cd = new CountDownLatch(10000);//倒计时
    
        public static class ParseDate implements Runnable {
            int i = 0;
    
            public ParseDate(int i) {
                this.i = i;
            }
    
            @Override
            public void run() {
                try {
                    if (t1.get() == null) {
                        t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
                            @Override
                            protected void finalize() throws Throwable {
                                System.out.println(this.toString() + " is gc");
                            }
                        });
                        System.out.println(Thread.currentThread().getId() + ":create SimpleDateFormat");
                    }
                    Date t = t1.get().parse("2016-12-19 19:29:" + i % 60);
                } catch (ParseException e) {
                    e.printStackTrace();
                } finally {
                    cd.countDown();//完成 计数器减1
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            ExecutorService es = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 10000; i++) {
                es.execute(new ParseDate(i));
            }
            cd.await();//等待所有线程 完成准备
            System.out.println("mission complete!!");
            t1 = null;
            System.gc();
            System.out.println("first GC complete!!");
            t1 = new ThreadLocal<>();
            cd = new CountDownLatch(1000);
            for (int i = 0; i < 10000; i++) {
                es.execute(new ParseDate(i));
            }
            cd.await();
            Thread.sleep(1000);
            System.gc();
            System.out.println("second GC complete!!");
        }
    }
         输出结果如下:
         在主函数Main中,先后进行了2次任务提交,每次10000个任务,在第一次任务提交后,我们t1设置为null 接着进行了一次gc,接着我们进行了第二次任务提交,完成后在进行一次gc,
         注意这些输出,.我们发现了当t1被设置为null时候,第一次gc 回收了.接着提交第二次任务,这次我们也是创建了10个线程,可以看到,虽然我们手动remove()这些对象,但是系统依然有可能回收他们.
         要了解这里的回收机制,我们需要进一步了解Thread.ThreadLocalMap的实现,之前我们说过,ThreadLocalMap是一个类似HashMap的东西,更精确地说,他更加类似WeakHashMap.
         ThreadLocalMap的实现使用了弱引用,弱引用是比强引用弱的多的引用,java在虚拟机回收时,如果发现若引用,就会立即回收,ThreadLocalMap内部由一系列Entry构成,每一个entry都是WeakReferenc<ThreadLocal>:
         
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
          这里的参数k就是map的key,v就是Map的value.其中k也就是ThreadLocal实例,作为弱引用使用(super(k)就是调用了WeakReferenc的构造函数,)因此,索然这里使用了ThreadLocal作为map的key,但是实际上,他并不是真的持有ThreadLocal的引用,而当THreadLocal的外部引用被回收时,ThreadLocalMap中的key就会变成null.当系统进行ThreadLocalMap清理时,就会自然将这些垃圾数据回收, 
  • 相关阅读:
    Socket网络编程--简单Web服务器(4)
    GCC学习笔记
    字符分隔符'1'(u0001)的困惑
    g++编译时遇到问题undefined reference to
    ROS学习笔记(三)
    cJSON笔记
    ROS学习笔记(二)
    ROS学习笔记(一)
    ffmpeg推流方式采用TCP协议
    Android OS的image文件组成
  • 原文地址:https://www.cnblogs.com/ten951/p/6212350.html
Copyright © 2011-2022 走看看