• java核心-多线程(9)- ThreadLocal类


    1.背景

        ThreadLocal类我想一般的码农或初级程序员在平时开发中基本上接触不到,但是面试老师会问、往高级点走会遇到这个类。这个类不是为了解决资源的竞争问题,而是为每个线程提供同一个容器,容器内部为每个线程提供一块空间,各个线程只能操作自己的空间,相互之间独立。
        这个类在java.lang包中。

    2.使用示例

        废话不多说,直接先看一下简单使用demo

    public class ThreadLocalTest {
    
            private static ThreadLocal<List<String>> threadLocal = new ThreadLocal<>();
    
        public void setThreadLocal(List<String> value){
            threadLocal.set(value);
        }
        public void getThreadLocal(){
            List<String> strings = threadLocal.get();
            strings.forEach(value -> System.out.println(Thread.currentThread().getName() + "###" + value));
        }
    
        public static void main(String[] args){
            System.out.println("test threadlocal");
            ThreadLocalTest threadLoaclTest = new ThreadLocalTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ArrayList<String> strings = new ArrayList<>();
                    strings.add("1");
                    strings.add("2");
                    strings.add("3");
                    threadLoaclTest.setThreadLocal(strings);
                    threadLoaclTest.getThreadLocal();
                }
            },"t1").start();
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    ArrayList<String> strings = new ArrayList<>();
                    strings.add("a");
                    strings.add("b");
                    strings.add("c");
                    threadLoaclTest.setThreadLocal(strings);
                    threadLoaclTest.getThreadLocal();
                }
            },"t2").start();
        }
        //ThreadLocal类型变量表面被多个线程使用,每个线程设置互相之间没有影响。因为它内部有一个ThreadLocalMap类型的变量映射了线程和它所设定的值。
        /*
        执行结果
            t1###1
            t1###2
            t1###3
            t2###a
            t2###b
            t2###c
         */
    }
    
    3.一些和threadlocal相关的面试总结,参考https://www.jianshu.com/p/3f0f9194d658
    11)什么是线程局部变量?
    
    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,每个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本,是线程隔离的。线程隔离的秘密在于ThreadLocalMap类(ThreadLocal的静态内部类)
    
    线程局部变量是局限于线程内部的变量,属于线程自身所有,不在多个线程间共享。Java 提供 ThreadLocal 类来支持线程局部变量,是一种实现线程安全的方式。但是在管理环境下(如 web 服务器)使用线程局部变量的时候要特别小心,在这种情况下,工作线程的生命周期比任何应用变量的生命周期都要长。任何线程局部变量一旦在工作完成后没有释放,Java 应用就存在内存泄露的风险。
    
    ThreadLocal的方法:void set(T value)、T get()以及T initialValue()。
    
    ThreadLocal是如何为每个线程创建变量的副本的:
    
    首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。
    
    总结:
    
    a、实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的
    
    b、为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;
    
    c、在进行get之前,必须先set,否则会报空指针异常;如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法(这一条是错误的,不会报错,而是返回null)
    
    4.简单看一下源码
    ThreadLocal源码分析
    
        1.public T get() {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);    //1.获取当前线程的ThreadLocalMap类型成员变量
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);  //2.这个map中可能存放多个ThreadLocal类型变量,map存放key就是ThreadLocal对象本身,而value就是ThreadLocal<T>的T类型数值
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
        ThreadLocalMap getMap(Thread t) {
            return t.threadLocals;   //3.每个线程对象中的ThreadLocalMap类型变量threadlocals是整个设计的灵魂,和面试官吹就好了
        }
    
        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;         //4.如果在set前,get的话得到的就是null
        }
    
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
    
    
    
        2.public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
        3.public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }
    
    //所以提到ThreadLocal类,在源码层面应该立即想到:在Thread类中有个ThreadLocal.ThreadLocalMap类型的变量threadlocals.
    
    5.ThreadLocal的应用场景

    这是最重要的,非常重要的,学的再好,不会用,甚至不知道用在哪,学来何用,不就是浪费精力吗?前面说过对于码农而言,在实际开发过程中不容易用到这个东西,如果你能清楚知道它用在什么地方,那么是不是就高级一点,这就是可以提高的地方。

    1.spring对有状态bean的管理
    Spring使用ThreadLocal解决线程安全问题。通常只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。参考:https://www.jianshu.com/p/f956857a8304
    
  • 相关阅读:
    JavaScript深入学习(四)DOM
    Python学习(六)简单例子
    Python学习(五)常见函数及控制结构
    Python学习(四)运算符
    Python学习(三)格式化输出
    Spark学习(二)scala语法
    JavaScript学习(三)深入学习
    表的完整性约束
    创建表的完整语法和基本数据类型
    mysql基础
  • 原文地址:https://www.cnblogs.com/leeethan/p/12342128.html
Copyright © 2020-2023  润新知