ThreadLocal.java介绍

2021-02-24  本文已影响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实现

上一篇下一篇

猜你喜欢

热点阅读