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
posted on 2020-02-22 18:12  火枪  阅读(122)  评论(0编辑  收藏  举报