zoukankan      html  css  js  c++  java
  • ThreadLocal源码分析

    1、概述

    ThreadLocal,可以理解为线程的局部变量,作用就是为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

    ThreadLocal是如何做到为每一个线程维护变量的副本的呢?

    每个线程中都有一个ThreadLocalMap(Thread.threadLocals),用于存储每一个线程的变量的副本。

    ThreadLocalMap使用数组Entry[] table保存ThreadLocal-->Object键值对象,数组保存位置:int i = key.nextHashCode() & (table.length - 1);。

    ThreadLocal和Synchonized区别

    都用于解决多线程并发访问。
    Synchronized用于线程间的数据共享(使变量或代码块在某一时该只能被一个线程访问),是一种以延长访问时间来换取线程安全性的策略;
    而ThreadLocal则用于线程间的数据隔离(为每一个线程都提供了变量的副本),是一种以空间来换取线程安全性的策略。

    2、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 table, resized as necessary.
             * table.length MUST always be a power of two.
             */
            private Entry[] table;
    
            /**
             * 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);
            }
    /**
             * Get the entry associated with key.  This method
             * itself handles only the fast path: a direct hit of existing
             * key. It otherwise relays to getEntryAfterMiss.  This is
             * designed to maximize performance for direct hits, in part
             * by making this method readily inlinable.
             *
             * @param  key the thread local object
             * @return the entry associated with key, or null if no such
             */
            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);
            }
    
            /**
             * Version of getEntry method for use when key is not found in
             * its direct hash slot.
             *
             * @param  key the thread local object
             * @param  i the table index for key's hash code
             * @param  e the entry at table[i]
             * @return the entry associated with key, or null if no such
             */
            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;
            }
    
            /**
             * Set the value associated with key.
             *
             * @param key the thread local object
             * @param value the value to be set
             */
            private void set(ThreadLocal<?> key, Object value) {
    
                // We don't use a fast path as with get() because it is at
                // least as common to use set() to create new entries as
                // it is to replace existing ones, in which case, a fast
                // path would fail more often than not.
    
                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) {
                        replaceStaleEntry(key, value, i);
                        return;
                    }
                }
    
                tab[i] = new Entry(key, value);
                int sz = ++size;
                if (!cleanSomeSlots(i, sz) && sz >= threshold)
                    rehash();
            }
    
            /**
             * 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;
                    }
                }
            }
    
            ...... 
        }

    3、ThreadLocal

    public class ThreadLocal<T> {
    
      /**
         * ThreadLocals rely on per-thread linear-probe hash maps attached
         * to each thread (Thread.threadLocals and
         * inheritableThreadLocals).  The ThreadLocal objects act as keys,
         * searched via threadLocalHashCode.  This is a custom hash code
         * (useful only within ThreadLocalMaps) that eliminates collisions
         * in the common case where consecutively constructed ThreadLocals
         * are used by the same threads, while remaining well-behaved in
         * less common cases.
         */
        private final int threadLocalHashCode = nextHashCode();
    
        /**
         * The next hash code to be given out. Updated atomically. Starts at
         * zero.
         */
        private static AtomicInteger nextHashCode =
            new AtomicInteger();
    
        /**
         * The difference between successively generated hash codes - turns
         * implicit sequential thread-local IDs into near-optimally spread
         * multiplicative hash values for power-of-two-sized tables.
         */
        private static final int HASH_INCREMENT = 0x61c88647;
    
        /**
         * Returns the next hash code.
         */
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
    
    
      /**
         * 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;
        }
    
        /**
         * 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 = t.threadLocals;
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;
                    return result;
                }
            }
            return setInitialValue();
        }
    
    
        /**
         * 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 = t.threadLocals;
            if (map != null)
                map.set(this, value);
            else
                createMap(t, value);
        }
    
        /**
         * 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 = Thread.currentThread().threadLocals;
             if (m != null)
                 m.remove(this);
         }
    
        /**
         * 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);
        }
    
      ......
    }

    为什么取HASH_INCREMENT = 0x61c88647?

    (可以阅读Why 0x61c88647?)

    由来:

    This number represents the golden ratio (sqrt(5)-1) times two to the power of 31. The result is then a golden number, either 2654435769 or -1640531527. You can see the calculation here:

    long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));  //Math.sqrt(5) - 1 = 1.2360679774997898
    System.out.println("as 32 bit unsigned: " + l1);  //2654435769
            
    int i1 = (int) l1;
    System.out.println("as 32 bit signed:   " + i1);  //-1640531527 = -0x61c88647

    与fibonacci hashing(斐波那契散列法)以及黄金分割有关,特殊的哈希码0x61c88647大大降低碰撞的几率,能让哈希码能均匀的分布在2的N次方的数组里。

    key.threadLocalHashCode & (len-1),ThreadLocalMap 中 Entry[] table 的大小必须是2的N次方呀(len = 2^N),那 len-1 的二进制表示就是低位连续的N个1, 那 key.threadLocalHashCode & (len-1) 的值就是 threadLocalHashCode 的低N位

    测试:

    private static AtomicInteger nextHashCode = new AtomicInteger();
    
        private static final int HASH_INCREMENT = 0x61c88647;
    
        private static int nextHashCode() {
            return nextHashCode.getAndAdd(HASH_INCREMENT);
        }
    
        public static void main(String[] args) {
            for (int j = 0; j < 5; j++) {
                int size = 2 << j;
                // hash = 0;
                int[] indexArray = new int[size];
                for (int i = 0; i < size; i++) {
                    indexArray[i] = nextHashCode() & (size - 1);
                }
                System.out.println("indexs = "+ Arrays.toString(indexArray));
            }
        }

    结果:

    indexs = [0, 1]
    indexs = [2, 1, 0, 3]
    indexs = [2, 1, 0, 7, 6, 5, 4, 3]
    indexs = [2, 9, 0, 7, 14, 5, 12, 3, 10, 1, 8, 15, 6, 13, 4, 11]
    indexs = [18, 25, 0, 7, 14, 21, 28, 3, 10, 17, 24, 31, 6, 13, 20, 27, 2, 9, 16, 23, 30, 5, 12, 19, 26, 1, 8, 15, 22, 29, 4, 11]

    没有看到重复的索引值,要哈希表的大小是2的N次方,那么基本上可以保证每次计算出的index值都不会重复。

    为什么HashCode不直接用自增的方式(HASH_INCREMENT=1)?

    我的理解是,随着不用的 ThreadLocal 变量被回收掉,这种自增的方式的性能会越来越差,因为临近的 slot 为空的可能性很小。而 ThreadLocal 实际所采用的方式,其下标是在跳跃分布,这样即使出现冲突,在临近找到空 slot 的可能性更大一些,性能也会更好。

    4、例子

    Android Looper的实现:

    public final class Looper {
      
       ...... // sThreadLocal.get() will return null unless you've called prepare(). static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static void prepare(boolean quitAllowed) { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(quitAllowed)); } /** * Return the Looper object associated with the current thread. Returns * null if the calling thread is not associated with a Looper. */ public static Looper myLooper() { return sThreadLocal.get(); }

       ...... }

     

    ThreadId:

    维护每个线程的id

    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();
            }
        }
  • 相关阅读:
    用Fiddler模拟低速网络环境
    定制 Fiddler 之将请求发往另一服务器
    软件测试流程进阶----两年软件测试总结
    Fiddler 4 抓包(APP HTTPS )
    JMeter报错 ERROR o.a.j.t.JMeterThread: Test failed!
    Python显示百分比
    python 2 到 3 的新手坑
    Python运行的17个时新手常见错误小结
    Python去除字符串的空格
    js控制媒体查询样式/判断是PC端还是移动端
  • 原文地址:https://www.cnblogs.com/John-Chen/p/4397224.html
Copyright © 2011-2022 走看看