美文网首页
ThreadLocal.java介绍

ThreadLocal.java介绍

作者: 旋转马达 | 来源:发表于2021-02-24 17:46 被阅读0次

    序言

    这个类提供线程局部变量。这些变量与普通变量的不同之处在于,每个访问这些变量的线程(通过其get或set方法)都有自己独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,它们希望将状态与线程(例如,用户ID或事务ID)关联起来。
    上面这段话是JDK源码中ThreadLocal类的javadoc的翻译版本,下面我们来解释一下该定义:

    1: 什么是线程局部变量?
    线程局部变量就是只有线程实例自己可以访问的变量,对其他线程是互斥的(无论是同类线程还是非同类线程)。

    2:为什么说ThreadLocal实例通常是类中的私有静态字段
    一般而言,ThreadLocal会被声明为private static final, 因为ThreadLocal有一个特殊作用-----作为map的key值。但是这个不是绝对的,根据需求来变化。

    一:ThreadLocal实现原理

    这里要说一句“违背”常理的结论,我们使用功能ThreadLocal 的时候都是直接使用get和set方法,很多人会人为变量是保存在ThreadLocal中的,但是实际上ThreadLocal不保存任何变量和数据,数据都保存在了Thread中,所以ThreadLocal这个名字就很应景----线程本地变量。

    每个Thread中都有一个ThreadLocalMap成员变量,叫做threadLocals,该变量用于保存和线程关联的数据,但是我们又不能直接访问这个变量,因为他不是public的,那我们应该怎么访问他呢?答案是通过ThreadLocal访问,这也就是ThreadLocal的核心作用----控制threadLocals的访问和初始化。

    看一段ThreadLocal的常规用法代码

    public class RequestContext {
        /**
         * ThreadLocal是单例的,因为ThreadLocalMap是用ThreadLocal作为key的,
         * 如果不是单例的,同一个线程就不可以创建多个包含ThreadLocal的类的实例,
         * 获取访问这样的实例的时候得到我们不期望的结果
         */
        private final static ThreadLocal<Map<String, PuppyRequest>> requestsLocalVar =
                ThreadLocal.withInitial(() -> {
                    Map<String, PuppyRequest> data = new HashMap<>();
                    System.out.println("线程" + Thread.currentThread().getName() +
                            "初始化map=" + System.identityHashCode(data));
                    return data;
                });
    
        private RequestContext() {
        }
    
        public static PuppyRequest getRequest() {
            Map<String, PuppyRequest> requestMap = requestsLocalVar.get();
            if (requestMap == null) {
                requestMap = new HashMap<>();
                requestsLocalVar.set(requestMap);
            }
            System.out.println("requestMap=" + System.identityHashCode(requestMap));
    
            PuppyRequest request = requestMap.get(Thread.currentThread().getName());
    
            if (request == null) {
                request = new PuppyRequest();
                requestMap.put(Thread.currentThread().getName(), request);
            }
    
            return request;
        }
    }
    

    这段代码中有两个核心部分,第一:声明requestsLocalVar 变量,第二:声明getRequest()方法。

    第一部分创建了一个ThreadLocal变量,注意:没有使用功能new,而是用ThreadLocal.withInitial,传递了一个lambda表达式,该表达式表示的是ThreadLocal的initialValue()方法要执行的代码。

    第二部分getRequest()方法,用于获取一个PuppyRequest对象,这个方法是支持并发访问的,并且没有使用锁,他先调用ThreadLocal的get方法,获取一个Map,如果map为空则创建一个map,在调用set方法,这里我们有疑问,这里没有加锁,那么多线程访问requestsLocalVar会不会发生数据错乱呢?
    答案是显然的,肯定不会,为什么,请看下面个ThreadLocal get方法讲解

    ThreadLocal#get()

    看代码实现

    /**
         * Returns the value in the current thread's copy of this
         * thread-local variable.  If the variable has no value for the
         * current thread, it is first initialized to the value returned
         * by an invocation of the {@link #initialValue} method.
         *
         * @return the current thread's value of this thread-local
         */
        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();
        }
    

    get方法的作用是访问该ThreadLocal关联的对象,如何访问的从代码中就可以看出来
    1.获取当前线程
    2.根据当前线程获取一个ThreadLocalMap
    3.如果ThreadLocalMap不等于null,则根据该ThreadLocal读取map中的一个Entry,
    4.返回entry中保存的value,这个value就是requestsLocalVar 变量声明的要本地化的Map<String, PuppyRequest>
    5.如果ThreadLocalMap等于null,则进行执行初始化的方法。初始化方法做了什么?

    看setInitialValue方法的代码

    /**
         * Variant of set() to establish initialValue. Used instead
         * of set() in case user has overridden the set() method.
         *
         * @return the initial value
         */
        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;
        }
    

    1.调用initialValue()方法得到一个类型为T的返回值
    2.根据当前线程获取ThreadLocalMap
    3.如果map不等于null,则将该ThreadLocal和前面提供的初始值关联起来,ThreadLocal作为key,前面的返回值作为value
    4.如果map等于空,则调用createMap(t,value)方法。
    不难看出createMap方法回去创建ThreadLocalMap,这样才能存在前面初始化方法提供的value,

    void createMap(Thread t, T firstValue) {
           t.threadLocals = new ThreadLocalMap(this, firstValue);
       }
    

    从createMap方法的代码可以看出,只是new了一个ThreadLocalMap,并赋值给线程对象的threadLocals对象。

    看到这里我们就应该明白了一个结论:
    ThreadLocal会为不同的Thread创建一个ThreadLocalMap来保存和当前线程相关的对象(状态),
    不同线程访问ThreadLocal的时候,ThreadLocal就会取到为该thread创建的map,然后从该map中取到
    对象,然后返回,这样就实现了线程本地变量,该变量保存的数据就只有该线程可以访问,用一种更为直观的方式理解set方法,请看如下代码

    public T get(Thread t) {}
    

    将ThreadLocal的get方法加上参数Thread,这样看起来就相当于取t的thread local(线程本地)变量

    ThreadLocal#set()

    看代码实现

    /**
         * Sets the current thread's copy of this thread-local variable
         * to the specified value.  Most subclasses will have no need to
         * override this method, relying solely on the {@link #initialValue}
         * method to set the values of thread-locals.
         *
         * @param value the value to be stored in the current thread's copy of
         *        this thread-local.
         */
        public void set(T value) {
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    

    1.取当前线程
    2.根据当前线程获取ThreadLocalMap
    3.如果map不等于null,则把ThreadLocal和value关联
    4.如果map等于null,则创建map,并把value保存的map

    看了上面的get方法,其实set方法也就更好理解了,同理看如下代码

    public T set(Thread t,T value) {}
    

    为线程t,保存一个本地变量value,下次就可以通过get方法,取回value。

    二:总结

    结论就是:
    每个线程都各自保存着自己相关的变量和值,ThreadLocal只是给我们开放了访问Thread变量的接口。Thread存储变量用的是ThreadLocalMap,该map是以ThreadLocal为key,Thread要保存的值为value的一个map实现

    相关文章

      网友评论

          本文标题:ThreadLocal.java介绍

          本文链接:https://www.haomeiwen.com/subject/zchkfltx.html