zoukankan      html  css  js  c++  java
  • Java多线程_ThreadLocal

    用法:
    ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

    • ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
    • ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
    • ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
    • ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
    public class MyThreadLocal
    {
        private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
            /**
             * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
             */
            @Override
            protected Object initialValue()
            {
                System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
                return null;
            }
        };
         
        public static void main(String[] args)
        {
            new Thread(new MyIntegerTask("IntegerTask1")).start();
            new Thread(new MyStringTask("StringTask1")).start();
            new Thread(new MyIntegerTask("IntegerTask2")).start();
            new Thread(new MyStringTask("StringTask2")).start();
        }
         
        public static class MyIntegerTask implements Runnable
        {
            private String name;
             
            MyIntegerTask(String name)
            {
                this.name = name;
            }
     
            @Override
            public void run()
            {
                for(int i = 0; i < 5; i++)
                {
                    // ThreadLocal.get方法获取线程变量
                    if(null == MyThreadLocal.threadLocal.get())
                    {
                        // ThreadLocal.et方法设置线程变量
                        MyThreadLocal.threadLocal.set(0);
                        System.out.println("线程" + name + ": 0");
                    }
                    else
                    {
                        int num = (Integer)MyThreadLocal.threadLocal.get();
                        MyThreadLocal.threadLocal.set(num + 1);
                        System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
                        if(i == 3)
                        {
                            MyThreadLocal.threadLocal.remove();
                        }
                    }
                    try
                    {
                        Thread.sleep(1000);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }  
            }
             
        }
         
        public static class MyStringTask implements Runnable
        {
            private String name;
             
            MyStringTask(String name)
            {
                this.name = name;
            }
     
            @Override
            public void run()
            {
                for(int i = 0; i < 5; i++)
                {
                    if(null == MyThreadLocal.threadLocal.get())
                    {
                        MyThreadLocal.threadLocal.set("a");
                        System.out.println("线程" + name + ": a");
                    }
                    else
                    {
                        String str = (String)MyThreadLocal.threadLocal.get();
                        MyThreadLocal.threadLocal.set(str + "a");
                        System.out.println("线程" + name + ": " + MyThreadLocal.threadLocal.get());
                    }
                    try
                    {
                        Thread.sleep(800);
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            }   
        }
    }
    

      结果:

     

    ThreadLocal的核心机制:

    • 每个Thread线程内部都有一个Map。
    • Map里面存储线程本地对象(key)和线程的变量副本(value)
    • Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。

    所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

    ThreadLocalMap
    ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独立的方式实现了Map的功能,其内部的Entry也独立实现。在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点被Entry的构造方法已经限定死了。

    static class Entry extends WeakReference<ThreadLocal> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal k, Object v) {
            super(k);
            value = v;
        }
    } 

    Entry继承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用类型的,Value并非弱引用。

    Hash冲突怎么解决?
    和HashMap的最大的不同在于,ThreadLocalMap结构非常简单,没有next引用,也就是说ThreadLocalMap中解决Hash冲突的方式并非链表的方式,而是采用线性探测的方式,所谓线性探测,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。


    ThreadLocalMap解决Hash冲突的方式就是简单的步长加1或减1,寻找下一个相邻的位置。

    /**
     * Increment i modulo len.
     */
    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }
    
    /**
     * Decrement i modulo len.
     */
    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }

    显然ThreadLocalMap采用线性探测的方式解决Hash冲突的效率很低,如果有大量不同的ThreadLocal对象放入map中时发送冲突,或者发生二次冲突,则效率很低。

    所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到map中的Key都是相同的ThreadLocal,如果一个线程要保存多个变量,就需要创建多个ThreadLocal,多个ThreadLocal放入Map中时会极大的增加Hash冲突的可能。

    ThreadLocalMap的问题
    由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。


    如何避免泄漏?
    既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
    如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

    总结

    1. 每个ThreadLocal只能保存一个变量副本,如果想要一个线程能够保存多个副本,就需要创建多个ThreadLocal。
    2. ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
    3. 适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。
  • 相关阅读:
    什么是95%的置信区间?
    机器学习博客
    深度自动编码器
    深度神经网络中的权重初始化方法
    自编码器和去噪自编码器的可视化
    08 scrapy框架
    redis.exceptions.DataError: Invalid input of type: 'dict'. Convert to a bytes, string, int or float first.
    Redis 教程
    selenium 滑动解锁(drag_and_drop_by_offset)
    获取登陆cookie,并且利用cookie访问登陆后的界面
  • 原文地址:https://www.cnblogs.com/ericz2j/p/10313597.html
Copyright © 2011-2022 走看看