zoukankan      html  css  js  c++  java
  • 线程应用:(十)ThreadLocal

    一、应用场景举例

      每个线程都应该有自己的数据库连接Connection,不能被其他线程所影响,就可以使用ThreadLocal(一个线程内各模块间共享同一数据,各线程间的数据又是独立的)。

    二、使用举例

      线程范围内的共享变量,每个线程有自己独立的数据。例如每个线程要有自己独立的连接。ThreadLocal本质上是一个map。

      只要定义一个ThreadLocal变量,往这个变量里放的数据就是和线程相关的。一个ThreadLocal只能放一个变量,如果要存多个变量可以利用实体类。结合单例模式,把ThreadLocal定义在实体类。

    public class ThreadTest4 {
        public static void main(String[] args){
            for(int i=0;i<2;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        int data = new Random().nextInt();
                        System.out.println(Thread.currentThread().getName()+"has put data:" + data);
                        Entity.getInstance().setData(data);        //拿到本线程在这个类里对应的实例,再赋值
                        new A().get();
                        new B().get();
                    }
                }).start();
            }
        }
    }
    
    class A {
        public void get(){
            Entity mydata = Entity.getInstance();
            System.out.println("A from "+Thread.currentThread().getName()+"has put data:" + mydata.getData());
        }
    }
    
    class B {
        public void get(){
            Entity mydata = Entity.getInstance();
            System.out.println("B from "+Thread.currentThread().getName()+"has put data:" + mydata.getData());
        }
    }
    
    //多变量实体类
    class Entity{
        private Entity(){}
        private static ThreadLocal<Entity> map = new ThreadLocal<Entity>();    //在这个类中定义一个ThreadLocal,存每个线程对应的实例
        public static /*synchronized*/ Entity getInstance(){    //一般单例加synchronized会更严谨一点,这里取的就是本线程的实体类,所以可不加
            Entity instance = map.get();
            if(instance == null){        //如果这个线程还没有对应的实例,才往ThreadLocal存
                instance = new Entity();
                map.set(instance);        
            }
            return instance;
        }
        int data;
        public int getData() {
            return data;
        }
        public void setData(int data) {
            this.data = data;
        }
    }
    
    运行结果:
    Thread-0has put data:2114567887
    Thread-1has put data:-746942766
    A from Thread-1has put data:-746942766
    A from Thread-0has put data:2114567887
    B from Thread-0has put data:2114567887
    B from Thread-1has put data:-746942766

    三、源码分析

    1、get()方法

    //ThreadLocal的get()方法
    public T get() {
        Thread t = Thread.currentThread();        //1、得到当前线程
        ThreadLocalMap map = getMap(t);            //2、从当前线程获得threadLocals成员变量
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);    //3、从threadLocals得到对应线程数据
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
    
    //2
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    //3
    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);
    }

      看完了get()方法,我发现ThreadLocal和我想象的不一样,我一开始以为ThreadLocal本身就是一个map数据结构,键是线程id,但其实真正存储数据的地方是在每个Thread对象的 threadLocals属性中,threadLocals 属性是一个 ThreadLocal.ThreadLocalMap 对象。并且通过ThreadLocal的threadLocalHashCode指定数据存储在数组的哪个位置下。

      可能接下来就有疑问:ThreadLocalMap的数据结构是怎么样的?又是怎么存储数据的呢?

    2、ThreadLocalMap数据结构

    //Thread类的成员变量threadLocals
    public class Thread implements Runnable {
    ...    
        ThreadLocal.ThreadLocalMap threadLocals = null;
    ...

      

    //ThreadLocal.ThreadLocalMap
    static class ThreadLocalMap {
    
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
    
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
    
        private static final int INITIAL_CAPACITY = 16;
    
        private Entry[] table;
    
    ...

      ThreadLocal.ThreadLocalMap 是 ThreadLocal 的一个静态内部类。可以看出,ThreadLocalMap实际是使用一个数组 private Entry[] table 来存储数据,初始大小为16,类型为Entry。

       对于Entry类型,一开始我看 Entry 只有一个value属性,就有一个疑问,为什么 Entry[] table 这个数组是一个map数组,而不是value数组。更仔细地查看源码后,发现Entry真的应该是一个键值对数组,并以 ThreadLocal<?>作为key,且这个key被弱引用类型包装 WeakReference<ThreadLocal<?>>。从ThreadLocalMap 的 getEntry() 方法可以看出进行了一次key的比对,而这里的key就是Entry的referent属性,且referent被一个弱引用包装。

    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);
    }
    
    public T get() {
        return this.referent;
    }

      综上,我们知道,ThreadLocal 并不是真正存放数据的数据结构,而是被弱引用包装,做为每个线程Thread对象中ThreadLocalMap类型属性的key。

      为什么不是把ThreadLocal当成map,以线程ID为key这样的方式,而是以这样的方式来存放数据,其实思考一下,是能发现其可取之处的,对于后一种方式,当线程结束后,相关的数据会跟随一起被回收。如果把ThreadLocal当成map,理论上也能实现,但就可能会出现线程结束但它的相关资源仍存在的情况。 

    3、set()方法

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    private void set(ThreadLocal<?> key, Object value) {
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);    //根据threadLocalHashCode找数组中对应的位置
    
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) {
            ThreadLocal<?> k = e.get();
    
            if (k == key) {                //看key是否存在,存在替换掉
                e.value = value;
                return;
            }
    
            if (k == null) {
                replaceStaleEntry(key, value, i);
                return;
            }
        }
    
        tab[i] = new Entry(key, value);        //不存在,新建
        int sz = ++size;
        if (!cleanSomeSlots(i, sz) && sz >= threshold)
            rehash();
    }

    四、ThreadLocal内存泄漏的问题

      根据上面Entry方法的源码,我们知道ThreadLocalMap是使用ThreadLocal的弱引用作为Key的。JVM中弱引用在垃圾回收时,不管内存有没有占满,都会被GC回收。因此很有可能在某次GC之后,某个线程的某个ThreadLocal变量变成了null,那么在Entry中,Key也变成了null,在查找时将永远不会被找到,这个Entry的Value将永远不会被用到,这就是内存泄漏。 

    为了防止此类情况的出现,我们有两种手段。

      1、手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露

      2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

      

    参考链接:

     http://www.cnblogs.com/digdeep/p/4510875.html

     https://www.cnblogs.com/qiuyong/p/7091689.html

     http://www.importnew.com/22039.html

     https://blog.csdn.net/lhqj1992/article/details/52451136

    https://blog.csdn.net/fly910905/article/details/78869251

    https://www.cnblogs.com/xzwblog/p/7227509.html

    https://www.cnblogs.com/coshaho/p/5127135.html

    https://www.cnblogs.com/dolphin0520/p/3920407.html

    http://blog.51cto.com/2179425/2082743

    https://blog.csdn.net/woshiluoye9/article/details/72544764

    https://blog.csdn.net/L_BestCoder/article/details/79252620

  • 相关阅读:
    js 创建函数,传递三个参数,返回最大值
    JS 实现 计算1~任意数字之间的所有整数阶乘的和
    npm install
    如何在浏览器上安装 VueDevtools工具
    前端日历插件
    css隐藏元素的几种方法
    less和sass的区别
    vue.js 自定义事件
    vue简单的导航栏
    用jetty启动idea中的maven项目报错Caused by: java.lang.ClassNotFoundException: org.apache.jasper.runtime.JspApplicationContextImpl
  • 原文地址:https://www.cnblogs.com/zjxiang/p/9452277.html
Copyright © 2011-2022 走看看