zoukankan      html  css  js  c++  java
  • ThreadLocal

    ThreadLocal(线程的隔离)

    ThreadLocal 使每一个线程有独立的副本:它 提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。

    ThreadLocal的应用场景:

    在多线程并发情况下,有一个共享变量,不同线程设置不同值后,各线程只获取自己设置的值

    代码演示:

    public class ThreadLocalTest {
    
        private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>(){
            @Override
            protected Integer initialValue() {
                return 1;
            }
        };
    
        /**
         * 测试线程,线程的工作是将ThreadLocal变量的值变化,并写回,看看线程之间是否会互相影响
         */
        private static class ThreadInfo extends  Thread{
            int num;
            public ThreadInfo(int num){
                this.num = num;
            }
            @Override
            public void run() {
                int tl = threadLocal.get();
                tl += num;
                threadLocal.set(tl);
                System.out.println(Thread.currentThread().getName()+"---threadLocal.get():"+threadLocal.get());
            }
        }
    
        public static void main(String[] args) {
            int times = 5;
            Thread[] threads = new Thread[times];
            for (int i = 0; i < times; i++) {
                threads[i] = new ThreadInfo(i);
            }
            for (int i = 0; i < times; i++) {
                threads[i].start();
            }
        }
    }

    ThreadLocal运行结果:

    从上面代码可以看出每个线程持有一个ThreadLocalMap对象。每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals则直接使用已经存在的对象

    每一个线程获取的ThreadLocal都是独立的。

    无ThreadLocal的代码演示:

    public class NoThreadLocal {
        static int noThreadLocal = 1;
    
        private  static class ThreadInfo extends Thread{
            public int num;
            public ThreadInfo(int num){
                this.num =  num;
            }
            @Override
            public void run() {
                noThreadLocal = noThreadLocal + num;
                System.out.println( Thread.currentThread().getName()+"---:"+noThreadLocal);
            }
        }
    
        public static void main(String[] args) {
            int times = 5;
            ThreadInfo[] threads = new ThreadInfo[times];
            for (int i = 0; i < times; i++) {
                threads[i] = new ThreadInfo(i);
            }
            for (int i = 0; i < times; i++) {
                threads[i].start();
            }
        }
    }

    无ThreadLocal运行结果:

    ThreadLocal的实现原理:

    ThreadLocal的set方法:第一步是获取当前线程实例,然后通过getMap方法获取当前线程实例的threadLocals属性,threadLocals是ThreadLocalMap类型的变量,而ThreadLocalMap则是一个定制化的HashMap。

    源码注释:将此线程局部变量的当前线程副本设置为指定值。大多数子类无需重写此方法,只需依赖{@link#initialValue}方法来设置线程局部变量的值。

        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }

    如果getMap方法获取的ThreadLocalMap类型变量map不等于null,则以当前ThreadLocal实例对象为key,传入的value值为value存到这个ThreadLocalMap中,需要注意的是在实际存储的时候,key使用的是ThreadLocal的弱引用。

    如果getMap方法获取的ThreadLocalMap类型变量map等于null,则调用createMap方法创建一个ThreadLocalMap实例对象,并以当前ThreadLocal实例对象为key,传入的value值为value存到这个ThreadLocalMap中。

    看到这里就会明白,使用ThreadLocal时,每个线程维护一个ThreadLocalMap映射表,映射表的key是ThreadLocal实例,并且使用的是ThreadLocal的弱引用 ,value是具体需要存储的Object。

    ThreadLocal的get方法:第一步是获取当前线程实例,然后获取当前线程副本中指定的值,如果getMap方法获取到的ThreadLocalMap对象实例等于null,则调用setInitialValue方法初始化一个ThreadLocalMap,并以当前ThreadLocal实例对象为key,null值为value存到这个ThreadLocalMap中,同时返回null。

    源码注释:返回此线程局部变量的当前线程副本中的值。如果变量没有当前线程的值,则首先将其初始化为调用{@link#initialValue}方法返回的值。

        public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }

    ThreadLocalMap map = getMap(t)的底层方法:重点注意这里是弱引用

    源码注释:此哈希映射中的条目使用它的主ref字段作为键(它总是线程本地对象)。注意空键(即entry.get()==null)表示不再引用密钥,因此条目可以从表中删除。这些条目被提及作为下面代码中的“陈旧条目”

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

    setInitialValue方法:返回ThreadLocal的初始值

    源码注释:用于建立初始值的set()的变量。取而代之以防用户重写set()方法

    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;
        }

    流程图说明:Thread类中声明的threadLocals变量是map结构,每个线程都可以关联很多个ThreadLocal变量。

     未完待续...

    参考网址:

    https://www.jianshu.com/p/3c5d7f09dfbd

    https://blog.csdn.net/windrui/article/details/105132387?depth_1-utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2&utm_source=distribute.pc_relevant.none-task-blog-OPENSEARCH-2

  • 相关阅读:
    快速认识ELK中的L
    HBase启动和停止命令
    Kafka 快速起步(作者:杜亦舒)
    Kafka 消息存储及检索(作者:杜亦舒)
    HBase集群搭建
    Kafka消息保证不丢失和重复消费问题
    Kafka文件的存储机制
    Kafka的配置文件详细描述
    kafka常用操作命令
    BZOJ1769 : [Ceoi2009]tri
  • 原文地址:https://www.cnblogs.com/mjtabu/p/12723554.html
Copyright © 2011-2022 走看看