Android线程篇(十)之面试必问ThreadLocal

作者: 小五666 | 来源:发表于2018-06-02 16:38 被阅读72次

Java面试必问ThreadLocal,所以想必大家对ThreadLocal并不陌生,从字面意思来看ThreadLocal很容易理解,但是想要真正理解并没有那么容易,今天我们就来扒下它的外衣……

1.首先我们来看看ThreadLocal如何使用,它能解决什么样的问题

ThreadLocal,线程本地变量,它可以为变量在每个线程中都创建一个副本,但它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。

来来来,For example:

 ThreadLocal<String> strLocal1=new ThreadLocal<>();
    public void setValue(){
        strLocal1.set(Thread.currentThread().getName());
    }
    public String getStrLocal1(){
        return strLocal1.get();
    }

    public void Run(){
        setValue();
        System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
        Thread thread=new Thread(){
            @Override
            public void run() {
                super.run();
                setValue();
                System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
            }
        };
        thread.start();
        System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
    }

看看Log:

<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4

从这段代码的输出结果可以看出,在main线程中和Thread-4线程中,strLocal1保存的副本值都不一样。最后一次在main线程再次打印副本值是为了证明在main线程中和Thread-4线程中的副本值确实是不同的。

ThreadLocal存储的值,在每个线程中互不影响,是不是很容易就实现线程安全。

我们来看下ThreadLocal的源码,扒下它的遮羞布

先来看看ThreadLocal提供的几个方法:

/**
     * 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() 方法第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。

注意:ThreadLocalMap.Entry e = map.getEntry(this);

这里传进去的是this,也就是ThreadLocal。

来看看getMap()方法中干了什么:

/**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。

那么我们继续去Thread类中取看一下成员变量threadLocals是什么:

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:

 /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

可以看到ThreadLocalMap的Entry继承了WeakReference,并且使用ThreadLocal作为键值。

WeakReference是个什么东西呢?

它就是Java里面传说的:弱引用,弱引用用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示,对于弱引用我们这里就不过多的讲解了,有时间我们也来扒下它神秘的外衣。

ThreadLocalMap中ThreadLocal做为key被保存在了WeakReference中,这就说明ThreadLocal在没有外部强引用时,发生GC时会被回收。

然后再继续看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;
    }

很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map。

先来看一下 T value = initialValue()

 /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

这里会返回null,会报空指针,所以我么在get()的时候,必须先set()
再来,看一下createMap的实现:

/**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

再来看看这一行:
t.threadLocals = new ThreadLocalMap(this, firstValue);

  * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:

首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

初始化时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
再来看看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);
    }

和setInitialValue()方法一样,如果map不为空,就设置键值对,为空,再创建Map,createMap(t, value)我们上面已经分析过了。

好了,该到总结的时候了:

1.通过ThreadLocal创建的副本,存储在每个线程自己的threadLocals中。

  1. threadLocals实际就是ThreadLocalMap,ThreadLocalMap把ThreadLocal做为key。

3.在进行get之前,必须先set,否则会报空指针异常。

4.如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。For example,修改一下上面的例子:

 ThreadLocal<String> strLocal1 = new ThreadLocal<String>() {
        @Override
        protected String initialValue() {
            return Thread.currentThread().getName();
        }
    };

//    public void setValue() {
//        strLocal1.set(Thread.currentThread().getName());
//        strLocal1.set("dddddd");
//    }

    public String getStrLocal1() {
        return strLocal1.get();
    }

    public void Run() {
//        setValue();
        System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
        Thread thread = new Thread() {
            @Override
            public void run() {
                super.run();
//                setValue();
                System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
            }
        };
        thread.start();
        System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
    }

贴上Log:

<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4

没有报错哦,赶紧动手试试吧。

相关文章

网友评论

    本文标题:Android线程篇(十)之面试必问ThreadLocal

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