zoukankan      html  css  js  c++  java
  • Java 8 ThreadLocal 源码解析

    概述

          ThreadLocal是一个本地线程副本变量工具类,很多地方称作线程本地变量,也有些地方称作线程本地存储。其原理就是为每个线程都提供一个副本变量,使得这些变量是线程级别的、私有的变量。所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了对副本的隔离,使得各个线程之间互不影响,从而在高并发场景下实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。如为每个线程创建一个独立的数据库连接。 

          我们来看一个并发问题:当一个可变对象被多个线程访问时,可能会得到非预期的结果。例如,在《避免创建不必要的对象》一文中,DateUtils 的两个方法 format(Date date)和parse(String strDate)都是非线程安全的,它们在格式化日期的时候,共享从父类 DateFormat 继承而来的 Calendar 对象。为了解决这个并发问题,文中给出了一种基于ThreadLocal的解决方案,本文在此基础上,从源码的角度分析ThreadLocal是怎么实现线程安全的,所用java.version为1.8.0_05。

    ThreadLocal get 源码解读

          在使用 ThreadLocal 时,当前线程通过 get() 方法访问 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); // 获取当前线程的ThreadLocalMap
           if (map != null) { // 判断 ThreadLocalMap 是否存在
                ThreadLocalMap.Entry e = map.getEntry(this); // 调用  ThreadLocalMap 的 getEntry 方法
    
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }

          首先,在①处取得当前线程t;然后,通过getMap(t)方法获取一个变量map,map的类型为ThreadLocalMap;其次,获取<key,value>键值对e,注意,这里获取键值对时,传进去的请求参数是 this,而非当前线程t。最后,判断map是否为null。如果map为null,则调用setInitialValue方法返回value;否则,返回value值。现在,对get方法的每一句来仔细分析。首先看一下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中的一个成员变量threadLocals,即

    public class Thread implements Runnable {
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. 
         */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }

          从注释可以看到,threadLocals就是ThreadLocal的内部类ThreadLocalMap。所以每个 Thread 都会拥有一个 ThreadLocalMap 变量,用于存放属于该 Thread 私有的 ThreadLocal 变量。因此,ThreadLocal就相当于一个调度中心,每次调用 get 方法的时候,都会先找到当前线程的 ThreadLocalMap,然后再在这个 ThreadLocalMap 中找到对应的线程本地变量。

          注意:ThreadLocal中之所以可以直接使用t.threadLocals,是因为Thread与ThreadLocal在同一个包下,同样Thread可以直接访问ThreadLocal.ThreadLocalMap threadLocals = null;来进行声明属性。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;
                }
            }
            /**
             * The initial capacity -- MUST be a power of two.
             */
            private static final int INITIAL_CAPACITY = 16;

          ThreadLocalMap 是ThreadLocal 内部的一个Map实现,然而它并没有实现任何集合的接口规范,因为它仅供内部使用,数据结构采用 数组 + 开方地址法,Entry 继承 WeakReference,是基于 ThreadLocal 这种特殊场景实现的 Map,以ThreadLocal作为key。

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

          显而易见,如果变量 map 不为空,就设置键值对;否则,调用createMap方法创建一个Map对象:

        /**
         * 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);
        }

          下面综述ThreadLocal是如何为每个线程创建变量副本的:

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

          初始化时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

          然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。 

    ThreadLocal set源码解读

        ThreadLocal 还提供了修改和删除当前包含对象的方法,修改的方法为 set,删除的方法为 remove:

        /**
         * 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); // 调用 ThreadLocalMap 的 set 方法
            else
                createMap(t, value);
        }

          很好理解,如果当前 ThredLocal 还没有包含值,那么就调用 createMap 来初始化当前线程的 ThreadLocalMap 对象;否则,直接在 map 中修改当前 ThreadLocal(this)包含的值。

    ThreadLocal remove源码解读 

        /**
         * Removes the current thread's value for this thread-local
         * variable.  If this thread-local variable is subsequently
         * {@linkplain #get read} by the current thread, its value will be
         * reinitialized by invoking its {@link #initialValue} method,
         * unless its value is {@linkplain #set set} by the current thread
         * in the interim.  This may result in multiple invocations of the
         * {@code initialValue} method in the current thread.
         *
         * @since 1.5
         */
         public void remove() {
             ThreadLocalMap m = getMap(Thread.currentThread());
             if (m != null)
                 m.remove(this);
         }

        remove 方法就是获得当前线程的 ThreadLocalMap 对象,然后调用这个 map 的remove(ThreadLocal) 方法。查看 ThreadLocalMap 的 remove(ThreadLocal) 方法的实现:

            /**
             * Remove the entry for key.
             */
            private void remove(ThreadLocal<?> key) {
                Entry[] tab = table;
                int len = tab.length;
                int i = key.threadLocalHashCode & (len-1);
                for (Entry e = tab[i];
                     e != null;
                     e = tab[i = nextIndex(i, len)]) {
                    if (e.get() == key) {
                        e.clear();
                        expungeStaleEntry(i);
                        return;
                    }
                }
            }

        关于remove(ThreadLocal<?> key),在下一节中再展开介绍。

    ThreadLocal和synchronized

         ThreadLocal和Synchonized都用于解决多线程并发访问。synchronized是利用锁的机制,使变量或代码块在某一时刻仅仅能被一个线程访问,依此实现数据共享。它以“时间换空间”,访问串行化,对象共享化。而ThreadLocal为每个线程都提供了变量的副本,使得每个线程在某一时刻访问到的并非同一个对象,这样就隔离了多个线程对数据的共享,以“空间换时间”,访问并行化,对象独享化,缺点是增加了线程间的竞争,降低了效率。

        Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。

     

    Reference

    1. https://blog.csdn.net/sonny543/article/details/51336457
    2. https://www.jianshu.com/p/56f64e3c1b6c
    3. https://segmentfault.com/a/1190000010251063?utm_medium=referral&utm_source=tuicool

     

  • 相关阅读:
    asp.net,cookie,写cookie,取cookie(转载)
    Powerdesign使用小技巧(转载)
    注册asp.net 4.0 到iis
    jQuery的弹出窗口插件colorbox
    sql自动增长标识(转载)
    SVN服务器更改ip地址客户端怎么设置(转载)
    IIS6.0应用程序池回收(转载)
    [delphi]修改indy源码后重新编译
    IdHttp 资料
    c/s程序版本自动升级的问题,如何判断client端版本号是否最新,然后从指定ftp服务器down
  • 原文地址:https://www.cnblogs.com/east7/p/11409571.html
Copyright © 2011-2022 走看看