ThreadLocal:线程局部变量

来自Wikioe
跳到导航 跳到搜索


关于

ThreadLocal 关键字的概念与作用,在Java 核心技术”(卷 I)的“并发”部分[1]深入理解 JVM 虚拟机”的“线程安全”部分[2]已经了解差不多了。


简单来说:ThreadLocal 就是通过定义“线程局部变量”的方式实现“线程本地存储”。


其作用(或者说应用场景)[3]:
1、变量隔离为变量在每个线程中创建一个副本,避免同一参数在所有线程中传递。
2、变量传递为线程开辟独立的存储空间,用于在同一线程的流程中保持上下文

源码相关

总结:

  1. Thread 类:
    • 属性:
      1. threadLocals(类型:ThreadLocal.ThreadLocalMap)用于维护“线程本地变量”。
  2. ThreadLocal 类:当前线程的 ThreadLocalMap 的访问入口
    • ThreadLocal 对象通常被定义为“private static”
    • 属性:
      1. 每个 ThreadLocal 对象都包含了一个独一无二的 threadLocalHashCode; ——【用于计算 ThreadLocal 对象在 ThreadLocalMap.table 中的位置
    • 方法:
      1. set(T value):设置“线程本地变量”的值,将调用 ThreadLocalMap 的 set(ThreadLocal<?> key, Object value)
      2. T get():获取“线程本地变量”的值,将调用 ThreadLocalMap 的 getEntry(ThreadLocal<?> key)
      3. remove()[4]:删除此“线程本地变量”,将调用 ThreadLocalMap 的 remove(ThreadLocal<?> key)
      • 泛型类,所以“线程本地变量”的值可以为各种类型
  3. ThreadLocalMap[5]存储了一组“K-V”映射(其实质是一个 Entry 数组
    • ThreadLocal 的内部类;
    • 属性:
      1. private Entry[] table;:Entry 数组,用于存储“K-V”映射。【key:“ThreadLocal对象”; value:“线程本地变量的值”】
    • 方法:
      1. set(ThreadLocal<?> key, Object value):向 table 中添加/设置 Entry;
      2. getEntry(ThreadLocal<?> key):从 table 中获取“ThreadLocal 对象”关联的 Entry;
      3. remove(ThreadLocal<?> key):从 table 中移除“ThreadLocal 对象”关联的 Entry;
  4. Entry 类:一个 Entry 就是一个“线程本地变量 -> 线程本地变量的值”的映射
    • ThreadLocalMap 的内部类,继承于:“WeakReference[6]
    • 属性:
      1. referent:(从 Reference 类中继承而来)保存“ThreadLocal对象”
      2. value:保存“线程本地变量的值”

源码

  1. Thread
    package java.lang;
    
    import java.lang.ref.Reference;
    import java.lang.ref.ReferenceQueue;
    import java.lang.ref.WeakReference;
    import ...
    
    public class Thread implements Runnable {
        ...
    
        /* 
    	 * 与此线程相关的 ThreadLocal 值。此映射由【ThreadLocal 类】维护。
    	 */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * 与此线程相关的 InheritableThreadLocal 值。此映射由【InheritableThreadLocal 类】维护。
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    	
    	...
    }
    
    • threadLocals:保存线程的“线程本地变量”。
    • inheritableThreadLocals:保存线程从父线程继承的“线程本地变量”。
      • InheritableThreadLocal 类:(继承了 ThreadLocal 类)提供从父线程到子线程的值继承:当创建子线程时,子线程接收父线程具有值的所有可继承的“线程本地变量”的初始值。
        • 通常情况下,孩子的值与父母的值相同;但是,通过重写该类中的“childValue”方法,可以使子级的值成为父级的任意函数。
  2. ThreadLocal
    package java.lang;
    import java.lang.ref.*;
    import java.util.Objects;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.function.Supplier;
    
    /**
     * 此类提供【“线程局部变量”】。
     * 这些变量与它们的正常对应变量不同,因为每个访问一个变量的线程(通过其{@code get}或{@code set}方法)都有自己的、独立初始化的变量副本。
     * 
     * @since   1.2
     */
    public class ThreadLocal<T> {
        /**
         * ThreadLocals 依赖于每个线程的 ThreadLocal.ThreadLocalMap(Thread.threadLocals 和 Thread.inheritableThreadLocals)。
    	 * ThreadLocal.ThreadLocalMap 中:
    	 *     ThreadLocal对象充当键,通过 threadLocalHashCode 进行搜索。
    	 *
    	 * 这是一个自定义哈希代码(仅在ThreadLocalMaps中有用):
    	 *     它可以在相同线程使用连续构造的ThreadLocals的常见情况下消除冲突,
    	 *     而在不太常见的情况下保持良好的行为。
         */
        private final int threadLocalHashCode = nextHashCode();
    
        // 要给出的下一个哈希代码(从零开始,自动更新)
        private static AtomicInteger nextHashCode =
            new AtomicInteger();
    
        // 连续生成的哈希代码之间的差值
        private static final int HASH_INCREMENT = 0x61c88647;
    
        /**
         * 返回下一个哈希代码
         */
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
    
    	...
    
        /**
         * 【获取:当前线程的“局部变量”(此 ThreadLocal)】
         */
        public T get() {
    		// 获取当前线程的 ThreadLocalMap
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
    		/*
    		 * 如果 ThreadLocalMap 不为空:
    		 *     获取“此 ThreadLocal”关联的 Entry 的值
    		 */
            if (map != null) {
    			// 从 ThreadLocalMap 中查找“此 ThreadLocal”关联的 Entry
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
    				// 获取 Entry 的值(即“此 ThreadLocal”的值)
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
    		/*
    		 * 如果 ThreadLocalMap 为空:
    		 *     使用“初始值”来为当前线程创建 ThreadLocalMap
    		 */
            return setInitialValue();
        }
    
        /**
         * 【设置:当前线程的“局部变量”(此 ThreadLocal)】
         */
        public void set(T value) {
    		// 获取当前线程的 ThreadLocalMap
            Thread t = Thread.currentThread();
            ThreadLocalMap map = getMap(t);
    
            if (map != null)
    			/*
    			 * 如果“线程的 ThreadLocalMap”不为空:
    			 *     向其中设置 Entry(关联“此 ThreadLocal”、值为 value)
    			 */
                map.set(this, value);
            else
    			/*
    			 * 如果 ThreadLocalMap 为空:
    			 *     使用 value 来为当前线程创建 ThreadLocalMap
    			 */
                createMap(t, value);
        }
    
        /**
         * 【移除:当前线程的“局部变量”(此 ThreadLocal)】
         */
        public void remove() {
    		// 获取当前线程的 ThreadLocalMap
            ThreadLocalMap m = getMap(Thread.currentThread());
    		/*
    		 * 如果 ThreadLocalMap 不为空:
    		 *     移除“此 ThreadLocal”关联的 Entry
    		 */
            if (m != null)
    			m.remove(this);
        }
    
        ThreadLocalMap getMap(Thread t) {
            return t.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;
        }
    
        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }
    
        ...
    
        /**
         * 内部类:【ThreadLocal.ThreadLocalMap】
    	 * 
    	 * ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。在ThreadLocal类之外不导出任何操作。
    	 * 该类是包私有的,以允许在Thread类中声明字段。
    	 * 为了帮助处理非常大且长期的使用,哈希表条目使用WeakReferences作为键。
    	 * 但是,由于不使用引用队列,只有当表空间开始耗尽时,才保证删除过时的条目。
         */
        static class ThreadLocalMap {
    
            /**
    		 * 内部类:【ThreadLocal.ThreadLocalMap.Entry】
             * 此哈希映射中的条目扩展 WeakReference,使用其主ref字段作为键(始终是ThreadLocal对象)。
    		 * 
    		 * 注意,空键(即entry.get()==null)意味着不再引用该键,所以可以从表中删除该项。
             */
            static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);
                    value = v;
                }
            }
    
            // 初始容量 —— 必须是2的幂。
            private static final int INITIAL_CAPACITY = 16;
    
            // 表,根据需要调整大小。table.length 必须始终是2的幂。
            private Entry[] table;
    
            // 表中的条目数。
            private int size = 0;
    
            // 阈值:用于调整大小的下一个size值。
            private int threshold; // Default to 0
    
            /**
             * 设置调整大小的阈值,以最坏情况下保持2/3的负载系数。
             */
            private void setThreshold(int len) {
                threshold = len * 2 / 3;
            }
    
    		...
    
            /**
             * 构造一个新映射:包含最初的(firstKey,firstValue)。
    		 * 
    		 * ThreadLocalMaps是延迟构建的,因此我们只有在至少有一个条目可以放入其中时才创建一个。
             */
            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。
    		 * 
    		 * 仅由createInheritedMap调用。
             */
            private ThreadLocalMap(ThreadLocalMap parentMap) {
                Entry[] parentTable = parentMap.table;
                int len = parentTable.length;
                setThreshold(len);
                table = new Entry[len];
    
                for (int j = 0; j < len; j++) {
                    Entry e = parentTable[j];
                    if (e != null) {
                        @SuppressWarnings("unchecked")
                        ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                        if (key != null) {
                            Object value = key.childValue(e.value);
                            Entry c = new Entry(key, value);
                            int h = key.threadLocalHashCode & (len - 1);
                            while (table[h] != null)
                                h = nextIndex(h, len);
                            table[h] = c;
                            size++;
                        }
                    }
                }
            }
    
            /**
             * 【获取与 ThreadLocal 关联的 Entry】
    		 * 
    		 * 此方法本身仅处理快速路径:
    		 *     直接命中现有键。否则它将中继到 getEntryAfterMiss。
    		 *     这是为了最大限度地提高直接命中的性能,部分原因是使该方法易于内联。
             */
            private Entry getEntry(ThreadLocal<?> key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    
            /**
             * 在其直接哈希槽中找不到键时使用的 getEntry 方法的版本。
             */
            private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
                Entry[] tab = table;
                int len = tab.length;
    
                while (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == key)
                        return e;
                    if (k == null)
    					// 清除过时的条目
                        expungeStaleEntry(i);
                    else
                        i = nextIndex(i, len);
                    e = tab[i];
                }
                return null;
            }
    
            /**
             * 【添加与 ThreadLocal 关联的 Entry】
             */
            private void set(ThreadLocal<?> key, Object value) {
                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)]) {
                    ThreadLocal<?> k = e.get();
    
                    if (k == key) {
                        e.value = value;
                        return;
                    }
    
                    if (k == null) {
    					// 将 set 操作期间遇到的过时项替换为指定 key 的项
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
    			// 试探性地扫描一些单元格以查找过时的条目。
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
    				// 重新包装 和/或 调整 table 大小。
                    rehash();
            }
    
            /**
             * 【移除与 ThreadLocal 关联的 Entry】
             */
            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;
                    }
                }
            }
    
            ...
        }
    }
    
    • ThreadLocal 实例通常是希望将状态与线程关联的类中的“私有静态”(private static)字段(例如,用户ID 或 事务ID):
      import java.util.concurrent.atomic.AtomicInteger;
      
      public class ThreadId {
          // Atomic integer containing the next thread ID to be assigned
          private static final AtomicInteger nextId = new AtomicInteger(0);
      
          // Thread local variable containing each thread's ID
          private static final ThreadLocal<Integer> threadId =
              new ThreadLocal<Integer>() {
                  @Override 
                  protected Integer initialValue() {
                      return nextId.getAndIncrement();
              }
          };
      
          // Returns the current thread's unique ID, assigning it if necessary
          public static int get() {
              return threadId.get();
          }
      }
      
      如上,类生成每个线程本地的唯一标识符。线程的id在第一次调用{@code ThreadId.get()}时被分配,并且在后续调用时保持不变。
    • 只要线程处于活动状态且 ThreadLocal 实例可访问,每个线程都保持对其线程本地变量副本的隐式引用;当一个线程离开后,它的所有线程本地实例副本都将被垃圾收集(除非存在对这些副本的其他引用)。

应用过程

参考

  1. 参考:【核心技术:并发#线程局部变量(ThreadLocal)
  2. 参考:【深入理解JVM:线程安全与锁优化#无同步方案
  3. 关于 ThreadLocal 的作用:
    1. 【变量隔离】:就是用于实现“线程安全”。
    2. 【变量传递】:参考【Hibernate笔记_4:核心知识#关于“Session_管理方式:绑定到本地线程”】(将业务层获取的 Session 传递到 DAO 层)。
  4. 为什么要求“ThreadLocalMap 对象使用完毕后及时调用 remove()?”
    1. 在使用线程池的时候,线程执行完毕放回线程池中,有可能在下一次执行时带入上一次的执行状态。
    2. 便于对线程的 ThreadLocalMap 对象的 table 及时清理,以释放内存。
  5. ThreadLocalMap 对象的清理
    ThreadLocalMap 的“get、set、remove”等方法中,会调用“expungeStaleEntry、replaceStaleEntry、cleanSomeSlots”等方法(根据“if (e != null && e.get() == null)”判断 Entry 所引用的 ThreadLocal 对象是否还存在)来对 table 进行清理,以及时释放内存。
  6. 关于Entry extends WeakReference<ThreadLocal<?>>

    WeakReference 标志性的特点是:
    不会影响到被应用对象的 GC 回收行为(即,只要对象被除 WeakReference 对象之外所有的对象解除引用后,该对象便可以被 GC 回收),
    只不过在被对象回收之后,WeakReference 实例获取被引用对象时会返回 null

    为什么 Entry 继承 WeakReference?
    ThreadLocal 对象的 GC 回收:如果使用“强引用”,ThreadLocal 对象在使用完毕,将会由于被 Entry 对象“强引用”所引用导致无法被 GC 回收。