zoukankan      html  css  js  c++  java
  • ThreadLocal使用和原理

    实现机制

    1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。

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

    2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。

        public T get() {
            //获取当前线程
            Thread t = Thread.currentThread();
            //每个线程拥有一个map,取出来的是线程t的ThreadLocal.ThreadLocalMap对象

    ThreadLocalMap map = getMap(t);
    if (map != null) {
                //this->ThreadLocal实例是作为map的key来使用的
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null)
                    return (T)e.value;
            }
            //初始化这个线程的ThreadLocalMap,并且返回null
            return setInitialValue();
        }    
    
        private T setInitialValue() {
            T value = initialValue();
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
            return value;
        }
    
        protected T initialValue() {
            return null;
        }

    3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                //一个thread.ThreadLocalMap 可以维护多个不同ThreadLocal的值
                map.set(this, value);
            else
                createMap(t, value);
        }    

    事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己线程的变量map中以ThreadLocal为key来取值,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。

    内存泄露

    ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。

    ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。

    为什么用弱引用?

    key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

    key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除,否则value内存溢出。

    由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

    使用举例

    public class TestThreadLocal {
    
        private static int j = 0;
    
        public static void main(String[] args) {
            final ThreadLocal<String> tl1 = new ThreadLocal<String>();
            final ThreadLocal<String> tl2 = new ThreadLocal<String>();
            ExecutorService es = Executors.newFixedThreadPool(10);
            for (int i = 0; i < 30; i++) {
                es.execute(new Runnable() {
                    @Override
                    public void run() {
                        synchronized (TestThreadLocal.class) {
                            tl1.set("tl1"+"-"+j);
                            tl2.set("tl2"+"-"+j);
                            System.out.println("当前线程" + Thread.currentThread()+"-"+ j + "放入data=" + tl1.get());
                            System.out.println("当前线程" + Thread.currentThread()+"-"+ j + "放入data=" + tl2.get());
                            j++;
                        }
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("当前线程" + Thread.currentThread() + ":取出data=" + tl1.get());
                        System.out.println("当前线程" + Thread.currentThread() + ":取出data=" + tl2.get());
                    }
    
                });
            }
        }
    }
  • 相关阅读:
    java.util报错
    mysql的sql_mode合理设置
    MySQL查询本周、上周、本月、上个月份数据的sql代码
    连接池配置
    js实现内容点击复制
    myeclipse 打开jsp文件出错
    Spring可二次开发常用接口、类及其源码详解
    Redis学习之Redis集群模式缺陷及其处理
    Redis学习之API学习及Jedis源码原理分析
    Redis学习之4种模式实践及机制解析(单机、主从、哨兵、集群)
  • 原文地址:https://www.cnblogs.com/wade-luffy/p/5755481.html
Copyright © 2011-2022 走看看