zoukankan      html  css  js  c++  java
  • 【Java】深入理解ThreadLocal

    一、前言

          要理解ThreadLocal,首先必须理解线程安全。线程可以看做是一个具有一定独立功能的处理过程,它是比进程更细度的单位。当程序以单线程运行的时候,我们不需要考虑线程安全。然而当一个进程中包含多个线程的时候,就需要考虑线程安全问题,因为此时线程可能会同时操作同一个资源,当两个或者两个以上线程同时操作一个资源的时候,就会造成冲突、不一致等问题,即线程不安全。

          解决线程安全问题,本质上就是解决资源共享问题,一般有以下手段:

          1)可重入(不依赖环境);2)互斥(同一时间段只允许一个线程使用);3)原子操作;4)Thread-Local

    二、Thread-Local

          Thread-Local是一个很简单的思想:如果一个资源会引起线程竞争,那就为每一个线程配备一个资源。这就是ThreadLocal需要做的事情。

    三、ThreadLocal的用法

          在理解ThreadLocal之前,首先看一下它的用法:

     1 public class ThreadLocalTest {
     2     public static ThreadLocal<Integer> intLocal = new ThreadLocal<Integer>() {
     3         @Override
     4         protected Integer initialValue() {
     5             // TODO Auto-generated method stub
     6             return 0;
     7         }
     8 
     9         @Override
    10         public Integer get() {
    11             // TODO Auto-generated method stub
    12             set(super.get() + 1);
    13             return super.get();
    14         }
    15 
    16         @Override
    17         public void set(Integer value) {
    18             // TODO Auto-generated method stub
    19             super.set(value);
    20         }
    21 
    22         @Override
    23         public void remove() {
    24             // TODO Auto-generated method stub
    25             super.remove();
    26         }
    27     };
    28 
    29     public static void main(String[] args) {
    30         // TODO Auto-generated method stub
    31         for(int index = 0; index < 3; index ++)
    32             new MyThread(index).start();
    33     }
    34 }
    35 
    36 class MyThread extends Thread{
    37     int id;
    38     
    39     public MyThread(int id){
    40         this.id = id;
    41     }
    42 
    43     @Override
    44     public void run() {
    45         // TODO Auto-generated method stub
    46         for(int index = 0; index < 3; index ++){
    47             System.out.println("Thread-" + id + " : " + ThreadLocalTest.intLocal.get());
    48             try {
    49                 Thread.sleep((int)(100 * Math.random()));
    50             } catch (InterruptedException e) {
    51                 // TODO Auto-generated catch block
    52                 e.printStackTrace();
    53             }
    54         }
    55     }
    56 }
    View Code

          其打印结果如下:

    1 Thread-1 : 1
    2 Thread-0 : 1
    3 Thread-2 : 1
    4 Thread-0 : 2
    5 Thread-2 : 2
    6 Thread-1 : 2
    7 Thread-2 : 3
    8 Thread-0 : 3
    9 Thread-1 : 3
    View Code

          这是一个很典型的问题,在学习多线程以及同步的时候,几乎所有的书本都会使用类似的一个例子:银行存钱取钱问题。我们知道当多线程并发操作一个int值的加减操作的时候,最后的数值会产生很大的不确定性,得不到最终正确的结果。

          而从例子中我们可以看到:每一个线程对int值的操作都是独立的,我们使用的只是同一个静态的intLocal类型!通过使用TreadLocal,我们可以为每一个线程提供独立的资源副本,从而完成对资源的“共享”操作。

          ThreadLocal类中可重载的方法只有四个:

          1)set():设置值,也就是说,我们选择将某个值设置为ThreadLocal类型的;

          2)get():将设置进去的值取出来;

          3)remove():我们不想将某个值设置为ThreadLocal了,移除掉;

          4)initialValue():如果get的时候还没有设置值,就使用这个方法进行初始化;

          使用过程简单明了,一般重载initialValue()提供一个初始值就可以了,其余方法不需要重载。

    四、ThreadLocal的实现

         看源码是最直接也是最有效的学习方式,不但可以掌握其原理,也可以学习Java源码精巧的实现方式。

         ThreadLocal的实现代码略长,我们选取重要的方法作为切入点:

     1     public T get() {
     2         Thread t = Thread.currentThread();
     3         ThreadLocalMap map = getMap(t);
     4         if (map != null) {
     5             ThreadLocalMap.Entry e = map.getEntry(this);
     6             if (e != null)
     7                 return (T)e.value;
     8         }
     9         return setInitialValue();
    10     }

           第一个方法是get()方法。这边出现了Thread和Map,联想到Thread的作用,大致应该可以猜到ThreadLocal的实现:维护一个Map,以Thread作为键,以变量作为值,每一次要使用变量的时候,就以当前的Thread作为键去取值,如果没有,就初始化一个值返回,如果有,就直接返回。

          事实上,ThreadLocal的实现思路的确大致如此。但是我们要做的事情其实更多:

         A.如果要设置更多的值怎么办?也就是说,我们有多种资源需要共享怎么办?

         B.为每一个线程共享一个资源,如何回收?

         我们言归正传,从源码中获取答案。

         get()方法的第3行中出现了一个ThreadLocalMap实例,它是从getMap()方法获取的,其方法如下:

    1 ThreadLocalMap getMap(Thread t) {
    2         return t.threadLocals;
    3     }

         t表示当前的线程,从Thread的源码中可以看到,的确是有一个ThreadLocalMap实例,其声明和注解如下:

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

         这个属性是专门为ThreadLocal类而存在的,而它的实现也存在于ThreadLocal中,是ThreadLocal的一个静态内部类。其类注释如下:

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

      它是一个定制的HashMap(自然具有HashMap的相关特性,比如自动扩增容量等)。

      前面提到A,B两个问题,从源码来看,A问题的解决方案就是,为每一个Thread维护一个HashMap,在这里就是维护一个ThreadLocalMap属性,这个属性的键是ThreadLocal,值就是资源副本,详细描述如下:

      每一个Thread都有一个ThreadLocalMap属性,这个属性是类似于HashMap的,它以ThreadLocal为键,以属于该线程的资源副本为值。我们可以这样看待ThreadLocal:ThreadLocal是为一组线程维护资源副本的对象,通过它,可以为每一个线程创建资源副本,也可以正确获得属于某一线程的资源副本。

      每一个ThreadLocal只能维护一个共享资源,一旦声明ThreadLocal实例,线程在调用的其get()方法获取资源副本的时候,就可以自动设置绑定到该线程本身。

      好,现在转了一小圈回到get方法()。get()方法的第2、3行很明显是获取属于当前线程的ThreadLocalMap,如果这个map不为空,我们就以当前的ThreadLocal为键,去获取相应的Entry,Entry是ThreadLocalMap的静态内部类,其定义如下:

    1 static class Entry extends WeakReference<ThreadLocal> {
    2             /** The value associated with this ThreadLocal. */
    3             Object value;
    4 
    5             Entry(ThreadLocal k, Object v) {
    6                 super(k);
    7                 value = v;
    8             }
    9         }

      它继承与弱引用,所以在get()方法里面如第7行一样调用e.value方法就可以获取实际的资源副本值。但是如果有一个为空,说明属于该线程的资源副本还不存在,则需要去创建资源副本,从代码中可以看到是调用setInitialValue()方法,其定义如下:

     1 /**
     2      * Variant of set() to establish initialValue. Used instead
     3      * of set() in case user has overridden the set() method.
     4      *
     5      * @return the initial value
     6      */
     7     private T setInitialValue() {
     8         T value = initialValue();
     9         Thread t = Thread.currentThread();
    10         ThreadLocalMap map = getMap(t);
    11         if (map != null)
    12             map.set(this, value);
    13         else
    14             createMap(t, value);
    15         return value;
    16     }

      第8行调用initialValue()方法初始化一个值,还记得在一开始的例子中,我们重载这个方法产生一个初始化的值么?

      接下来是判断线程的ThreadLocalMap是否为空,不为空就直接这是值(键为this,值为value),为空则创建一个Map,调用方法为createMap(),其定义如下:

    1 void createMap(Thread t, T firstValue) {
    2         t.threadLocals = new ThreadLocalMap(this, firstValue);
    3     }

      简单明了,而ThreadLocalMap的这个构造方法的实现如下:

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

      实例化table数组用于存储键值对,然后通过映射将键值对存储进入相应的位置。

      至于set方法,看完get()后应该很简单了,自己都可以实现:

     1 /**
     2      * Sets the current thread's copy of this thread-local variable
     3      * to the specified value.  Most subclasses will have no need to
     4      * override this method, relying solely on the {@link #initialValue}
     5      * method to set the values of thread-locals.
     6      *
     7      * @param value the value to be stored in the current thread's copy of
     8      *        this thread-local.
     9      */
    10     public void set(T value) {
    11         Thread t = Thread.currentThread();
    12         ThreadLocalMap map = getMap(t);
    13         if (map != null)
    14             map.set(this, value);
    15         else
    16             createMap(t, value);
    17     }

      现在还剩一个问题B,会造成内存泄露吗?ThreadLocal的类注释里面有一段话:

    /* Each thread holds an implicit reference to its copy of a thread-local
     * variable as long as the thread is alive and the <tt>ThreadLocal</tt>
     * instance is accessible; after a thread goes away, all of its copies of
     * thread-local instances are subject to garbage collection (unless other
     * references to these copies exist).
    */

      每一个线程对资源副本都有一个隐式引用:只要线程还在运行,只要ThreadLocal还是可以获取的。当一个线程运行结束销毁时,所有的资源副本都是可以被垃圾回收的。这段注释表明,ThreadLocal的使用是不会造成内训泄露的。

      但是我们来仔细分析一下,我想画一张Thread、ThreadLocal和ThreadLocalMap的依赖关系图,但是在绘制过程中,我发现:根本就没有依赖!

      非常惊讶,我在这里把ThreadLocal完整的源码贴一遍,读者可以自行审视。

      1 package java.lang;
      2 import java.lang.ref.*;
      3 import java.util.concurrent.atomic.AtomicInteger;
      4 
      5 /**
      6  * This class provides thread-local variables.  These variables differ from
      7  * their normal counterparts in that each thread that accesses one (via its
      8  * <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized
      9  * copy of the variable.  <tt>ThreadLocal</tt> instances are typically private
     10  * static fields in classes that wish to associate state with a thread (e.g.,
     11  * a user ID or Transaction ID).
     12  *
     13  * <p>For example, the class below generates unique identifiers local to each
     14  * thread.
     15  * A thread's id is assigned the first time it invokes <tt>ThreadId.get()</tt>
     16  * and remains unchanged on subsequent calls.
     17  * <pre>
     18  * import java.util.concurrent.atomic.AtomicInteger;
     19  *
     20  * public class ThreadId {
     21  *     // Atomic integer containing the next thread ID to be assigned
     22  *     private static final AtomicInteger nextId = new AtomicInteger(0);
     23  *
     24  *     // Thread local variable containing each thread's ID
     25  *     private static final ThreadLocal&lt;Integer> threadId =
     26  *         new ThreadLocal&lt;Integer>() {
     27  *             &#64;Override protected Integer initialValue() {
     28  *                 return nextId.getAndIncrement();
     29  *         }
     30  *     };
     31  *
     32  *     // Returns the current thread's unique ID, assigning it if necessary
     33  *     public static int get() {
     34  *         return threadId.get();
     35  *     }
     36  * }
     37  * </pre>
     38  * <p>Each thread holds an implicit reference to its copy of a thread-local
     39  * variable as long as the thread is alive and the <tt>ThreadLocal</tt>
     40  * instance is accessible; after a thread goes away, all of its copies of
     41  * thread-local instances are subject to garbage collection (unless other
     42  * references to these copies exist).
     43  *
     44  * @author  Josh Bloch and Doug Lea
     45  * @since   1.2
     46  */
     47 public class ThreadLocal<T> {
     48     /**
     49      * ThreadLocals rely on per-thread linear-probe hash maps attached
     50      * to each thread (Thread.threadLocals and
     51      * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     52      * searched via threadLocalHashCode.  This is a custom hash code
     53      * (useful only within ThreadLocalMaps) that eliminates collisions
     54      * in the common case where consecutively constructed ThreadLocals
     55      * are used by the same threads, while remaining well-behaved in
     56      * less common cases.
     57      */
     58     private final int threadLocalHashCode = nextHashCode();
     59 
     60     /**
     61      * The next hash code to be given out. Updated atomically. Starts at
     62      * zero.
     63      */
     64     private static AtomicInteger nextHashCode =
     65         new AtomicInteger();
     66 
     67     /**
     68      * The difference between successively generated hash codes - turns
     69      * implicit sequential thread-local IDs into near-optimally spread
     70      * multiplicative hash values for power-of-two-sized tables.
     71      */
     72     private static final int HASH_INCREMENT = 0x61c88647;
     73 
     74     /**
     75      * Returns the next hash code.
     76      */
     77     private static int nextHashCode() {
     78         return nextHashCode.getAndAdd(HASH_INCREMENT);
     79     }
     80 
     81     /**
     82      * Returns the current thread's "initial value" for this
     83      * thread-local variable.  This method will be invoked the first
     84      * time a thread accesses the variable with the {@link #get}
     85      * method, unless the thread previously invoked the {@link #set}
     86      * method, in which case the <tt>initialValue</tt> method will not
     87      * be invoked for the thread.  Normally, this method is invoked at
     88      * most once per thread, but it may be invoked again in case of
     89      * subsequent invocations of {@link #remove} followed by {@link #get}.
     90      *
     91      * <p>This implementation simply returns <tt>null</tt>; if the
     92      * programmer desires thread-local variables to have an initial
     93      * value other than <tt>null</tt>, <tt>ThreadLocal</tt> must be
     94      * subclassed, and this method overridden.  Typically, an
     95      * anonymous inner class will be used.
     96      *
     97      * @return the initial value for this thread-local
     98      */
     99     protected T initialValue() {
    100         return null;
    101     }
    102 
    103     /**
    104      * Creates a thread local variable.
    105      */
    106     public ThreadLocal() {
    107     }
    108 
    109     /**
    110      * Returns the value in the current thread's copy of this
    111      * thread-local variable.  If the variable has no value for the
    112      * current thread, it is first initialized to the value returned
    113      * by an invocation of the {@link #initialValue} method.
    114      *
    115      * @return the current thread's value of this thread-local
    116      */
    117     public T get() {
    118         Thread t = Thread.currentThread();
    119         ThreadLocalMap map = getMap(t);
    120         if (map != null) {
    121             ThreadLocalMap.Entry e = map.getEntry(this);
    122             if (e != null)
    123                 return (T)e.value;
    124         }
    125         return setInitialValue();
    126     }
    127 
    128     /**
    129      * Variant of set() to establish initialValue. Used instead
    130      * of set() in case user has overridden the set() method.
    131      *
    132      * @return the initial value
    133      */
    134     private T setInitialValue() {
    135         T value = initialValue();
    136         Thread t = Thread.currentThread();
    137         ThreadLocalMap map = getMap(t);
    138         if (map != null)
    139             map.set(this, value);
    140         else
    141             createMap(t, value);
    142         return value;
    143     }
    144 
    145     /**
    146      * Sets the current thread's copy of this thread-local variable
    147      * to the specified value.  Most subclasses will have no need to
    148      * override this method, relying solely on the {@link #initialValue}
    149      * method to set the values of thread-locals.
    150      *
    151      * @param value the value to be stored in the current thread's copy of
    152      *        this thread-local.
    153      */
    154     public void set(T value) {
    155         Thread t = Thread.currentThread();
    156         ThreadLocalMap map = getMap(t);
    157         if (map != null)
    158             map.set(this, value);
    159         else
    160             createMap(t, value);
    161     }
    162 
    163     /**
    164      * Removes the current thread's value for this thread-local
    165      * variable.  If this thread-local variable is subsequently
    166      * {@linkplain #get read} by the current thread, its value will be
    167      * reinitialized by invoking its {@link #initialValue} method,
    168      * unless its value is {@linkplain #set set} by the current thread
    169      * in the interim.  This may result in multiple invocations of the
    170      * <tt>initialValue</tt> method in the current thread.
    171      *
    172      * @since 1.5
    173      */
    174      public void remove() {
    175          ThreadLocalMap m = getMap(Thread.currentThread());
    176          if (m != null)
    177              m.remove(this);
    178      }
    179 
    180     /**
    181      * Get the map associated with a ThreadLocal. Overridden in
    182      * InheritableThreadLocal.
    183      *
    184      * @param  t the current thread
    185      * @return the map
    186      */
    187     ThreadLocalMap getMap(Thread t) {
    188         return t.threadLocals;
    189     }
    190 
    191     /**
    192      * Create the map associated with a ThreadLocal. Overridden in
    193      * InheritableThreadLocal.
    194      *
    195      * @param t the current thread
    196      * @param firstValue value for the initial entry of the map
    197      * @param map the map to store.
    198      */
    199     void createMap(Thread t, T firstValue) {
    200         t.threadLocals = new ThreadLocalMap(this, firstValue);
    201     }
    202 
    203     /**
    204      * Factory method to create map of inherited thread locals.
    205      * Designed to be called only from Thread constructor.
    206      *
    207      * @param  parentMap the map associated with parent thread
    208      * @return a map containing the parent's inheritable bindings
    209      */
    210     static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    211         return new ThreadLocalMap(parentMap);
    212     }
    213 
    214     /**
    215      * Method childValue is visibly defined in subclass
    216      * InheritableThreadLocal, but is internally defined here for the
    217      * sake of providing createInheritedMap factory method without
    218      * needing to subclass the map class in InheritableThreadLocal.
    219      * This technique is preferable to the alternative of embedding
    220      * instanceof tests in methods.
    221      */
    222     T childValue(T parentValue) {
    223         throw new UnsupportedOperationException();
    224     }
    225 
    226     /**
    227      * ThreadLocalMap is a customized hash map suitable only for
    228      * maintaining thread local values. No operations are exported
    229      * outside of the ThreadLocal class. The class is package private to
    230      * allow declaration of fields in class Thread.  To help deal with
    231      * very large and long-lived usages, the hash table entries use
    232      * WeakReferences for keys. However, since reference queues are not
    233      * used, stale entries are guaranteed to be removed only when
    234      * the table starts running out of space.
    235      */
    236     static class ThreadLocalMap {
    237 
    238         /**
    239          * The entries in this hash map extend WeakReference, using
    240          * its main ref field as the key (which is always a
    241          * ThreadLocal object).  Note that null keys (i.e. entry.get()
    242          * == null) mean that the key is no longer referenced, so the
    243          * entry can be expunged from table.  Such entries are referred to
    244          * as "stale entries" in the code that follows.
    245          */
    246         static class Entry extends WeakReference<ThreadLocal> {
    247             /** The value associated with this ThreadLocal. */
    248             Object value;
    249 
    250             Entry(ThreadLocal k, Object v) {
    251                 super(k);
    252                 value = v;
    253             }
    254         }
    255 
    256         /**
    257          * The initial capacity -- MUST be a power of two.
    258          */
    259         private static final int INITIAL_CAPACITY = 16;
    260 
    261         /**
    262          * The table, resized as necessary.
    263          * table.length MUST always be a power of two.
    264          */
    265         private Entry[] table;
    266 
    267         /**
    268          * The number of entries in the table.
    269          */
    270         private int size = 0;
    271 
    272         /**
    273          * The next size value at which to resize.
    274          */
    275         private int threshold; // Default to 0
    276 
    277         /**
    278          * Set the resize threshold to maintain at worst a 2/3 load factor.
    279          */
    280         private void setThreshold(int len) {
    281             threshold = len * 2 / 3;
    282         }
    283 
    284         /**
    285          * Increment i modulo len.
    286          */
    287         private static int nextIndex(int i, int len) {
    288             return ((i + 1 < len) ? i + 1 : 0);
    289         }
    290 
    291         /**
    292          * Decrement i modulo len.
    293          */
    294         private static int prevIndex(int i, int len) {
    295             return ((i - 1 >= 0) ? i - 1 : len - 1);
    296         }
    297 
    298         /**
    299          * Construct a new map initially containing (firstKey, firstValue).
    300          * ThreadLocalMaps are constructed lazily, so we only create
    301          * one when we have at least one entry to put in it.
    302          */
    303         ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
    304             table = new Entry[INITIAL_CAPACITY];
    305             int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
    306             table[i] = new Entry(firstKey, firstValue);
    307             size = 1;
    308             setThreshold(INITIAL_CAPACITY);
    309         }
    310 
    311         /**
    312          * Construct a new map including all Inheritable ThreadLocals
    313          * from given parent map. Called only by createInheritedMap.
    314          *
    315          * @param parentMap the map associated with parent thread.
    316          */
    317         private ThreadLocalMap(ThreadLocalMap parentMap) {
    318             Entry[] parentTable = parentMap.table;
    319             int len = parentTable.length;
    320             setThreshold(len);
    321             table = new Entry[len];
    322 
    323             for (int j = 0; j < len; j++) {
    324                 Entry e = parentTable[j];
    325                 if (e != null) {
    326                     ThreadLocal key = e.get();
    327                     if (key != null) {
    328                         Object value = key.childValue(e.value);
    329                         Entry c = new Entry(key, value);
    330                         int h = key.threadLocalHashCode & (len - 1);
    331                         while (table[h] != null)
    332                             h = nextIndex(h, len);
    333                         table[h] = c;
    334                         size++;
    335                     }
    336                 }
    337             }
    338         }
    339 
    340         /**
    341          * Get the entry associated with key.  This method
    342          * itself handles only the fast path: a direct hit of existing
    343          * key. It otherwise relays to getEntryAfterMiss.  This is
    344          * designed to maximize performance for direct hits, in part
    345          * by making this method readily inlinable.
    346          *
    347          * @param  key the thread local object
    348          * @return the entry associated with key, or null if no such
    349          */
    350         private Entry getEntry(ThreadLocal key) {
    351             int i = key.threadLocalHashCode & (table.length - 1);
    352             Entry e = table[i];
    353             if (e != null && e.get() == key)
    354                 return e;
    355             else
    356                 return getEntryAfterMiss(key, i, e);
    357         }
    358 
    359         /**
    360          * Version of getEntry method for use when key is not found in
    361          * its direct hash slot.
    362          *
    363          * @param  key the thread local object
    364          * @param  i the table index for key's hash code
    365          * @param  e the entry at table[i]
    366          * @return the entry associated with key, or null if no such
    367          */
    368         private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) {
    369             Entry[] tab = table;
    370             int len = tab.length;
    371 
    372             while (e != null) {
    373                 ThreadLocal k = e.get();
    374                 if (k == key)
    375                     return e;
    376                 if (k == null)
    377                     expungeStaleEntry(i);
    378                 else
    379                     i = nextIndex(i, len);
    380                 e = tab[i];
    381             }
    382             return null;
    383         }
    384 
    385         /**
    386          * Set the value associated with key.
    387          *
    388          * @param key the thread local object
    389          * @param value the value to be set
    390          */
    391         private void set(ThreadLocal key, Object value) {
    392 
    393             // We don't use a fast path as with get() because it is at
    394             // least as common to use set() to create new entries as
    395             // it is to replace existing ones, in which case, a fast
    396             // path would fail more often than not.
    397 
    398             Entry[] tab = table;
    399             int len = tab.length;
    400             int i = key.threadLocalHashCode & (len-1);
    401 
    402             for (Entry e = tab[i];
    403                  e != null;
    404                  e = tab[i = nextIndex(i, len)]) {
    405                 ThreadLocal k = e.get();
    406 
    407                 if (k == key) {
    408                     e.value = value;
    409                     return;
    410                 }
    411 
    412                 if (k == null) {
    413                     replaceStaleEntry(key, value, i);
    414                     return;
    415                 }
    416             }
    417 
    418             tab[i] = new Entry(key, value);
    419             int sz = ++size;
    420             if (!cleanSomeSlots(i, sz) && sz >= threshold)
    421                 rehash();
    422         }
    423 
    424         /**
    425          * Remove the entry for key.
    426          */
    427         private void remove(ThreadLocal key) {
    428             Entry[] tab = table;
    429             int len = tab.length;
    430             int i = key.threadLocalHashCode & (len-1);
    431             for (Entry e = tab[i];
    432                  e != null;
    433                  e = tab[i = nextIndex(i, len)]) {
    434                 if (e.get() == key) {
    435                     e.clear();
    436                     expungeStaleEntry(i);
    437                     return;
    438                 }
    439             }
    440         }
    441 
    442         /**
    443          * Replace a stale entry encountered during a set operation
    444          * with an entry for the specified key.  The value passed in
    445          * the value parameter is stored in the entry, whether or not
    446          * an entry already exists for the specified key.
    447          *
    448          * As a side effect, this method expunges all stale entries in the
    449          * "run" containing the stale entry.  (A run is a sequence of entries
    450          * between two null slots.)
    451          *
    452          * @param  key the key
    453          * @param  value the value to be associated with key
    454          * @param  staleSlot index of the first stale entry encountered while
    455          *         searching for key.
    456          */
    457         private void replaceStaleEntry(ThreadLocal key, Object value,
    458                                        int staleSlot) {
    459             Entry[] tab = table;
    460             int len = tab.length;
    461             Entry e;
    462 
    463             // Back up to check for prior stale entry in current run.
    464             // We clean out whole runs at a time to avoid continual
    465             // incremental rehashing due to garbage collector freeing
    466             // up refs in bunches (i.e., whenever the collector runs).
    467             int slotToExpunge = staleSlot;
    468             for (int i = prevIndex(staleSlot, len);
    469                  (e = tab[i]) != null;
    470                  i = prevIndex(i, len))
    471                 if (e.get() == null)
    472                     slotToExpunge = i;
    473 
    474             // Find either the key or trailing null slot of run, whichever
    475             // occurs first
    476             for (int i = nextIndex(staleSlot, len);
    477                  (e = tab[i]) != null;
    478                  i = nextIndex(i, len)) {
    479                 ThreadLocal k = e.get();
    480 
    481                 // If we find key, then we need to swap it
    482                 // with the stale entry to maintain hash table order.
    483                 // The newly stale slot, or any other stale slot
    484                 // encountered above it, can then be sent to expungeStaleEntry
    485                 // to remove or rehash all of the other entries in run.
    486                 if (k == key) {
    487                     e.value = value;
    488 
    489                     tab[i] = tab[staleSlot];
    490                     tab[staleSlot] = e;
    491 
    492                     // Start expunge at preceding stale entry if it exists
    493                     if (slotToExpunge == staleSlot)
    494                         slotToExpunge = i;
    495                     cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    496                     return;
    497                 }
    498 
    499                 // If we didn't find stale entry on backward scan, the
    500                 // first stale entry seen while scanning for key is the
    501                 // first still present in the run.
    502                 if (k == null && slotToExpunge == staleSlot)
    503                     slotToExpunge = i;
    504             }
    505 
    506             // If key not found, put new entry in stale slot
    507             tab[staleSlot].value = null;
    508             tab[staleSlot] = new Entry(key, value);
    509 
    510             // If there are any other stale entries in run, expunge them
    511             if (slotToExpunge != staleSlot)
    512                 cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
    513         }
    514 
    515         /**
    516          * Expunge a stale entry by rehashing any possibly colliding entries
    517          * lying between staleSlot and the next null slot.  This also expunges
    518          * any other stale entries encountered before the trailing null.  See
    519          * Knuth, Section 6.4
    520          *
    521          * @param staleSlot index of slot known to have null key
    522          * @return the index of the next null slot after staleSlot
    523          * (all between staleSlot and this slot will have been checked
    524          * for expunging).
    525          */
    526         private int expungeStaleEntry(int staleSlot) {
    527             Entry[] tab = table;
    528             int len = tab.length;
    529 
    530             // expunge entry at staleSlot
    531             tab[staleSlot].value = null;
    532             tab[staleSlot] = null;
    533             size--;
    534 
    535             // Rehash until we encounter null
    536             Entry e;
    537             int i;
    538             for (i = nextIndex(staleSlot, len);
    539                  (e = tab[i]) != null;
    540                  i = nextIndex(i, len)) {
    541                 ThreadLocal k = e.get();
    542                 if (k == null) {
    543                     e.value = null;
    544                     tab[i] = null;
    545                     size--;
    546                 } else {
    547                     int h = k.threadLocalHashCode & (len - 1);
    548                     if (h != i) {
    549                         tab[i] = null;
    550 
    551                         // Unlike Knuth 6.4 Algorithm R, we must scan until
    552                         // null because multiple entries could have been stale.
    553                         while (tab[h] != null)
    554                             h = nextIndex(h, len);
    555                         tab[h] = e;
    556                     }
    557                 }
    558             }
    559             return i;
    560         }
    561 
    562         /**
    563          * Heuristically scan some cells looking for stale entries.
    564          * This is invoked when either a new element is added, or
    565          * another stale one has been expunged. It performs a
    566          * logarithmic number of scans, as a balance between no
    567          * scanning (fast but retains garbage) and a number of scans
    568          * proportional to number of elements, that would find all
    569          * garbage but would cause some insertions to take O(n) time.
    570          *
    571          * @param i a position known NOT to hold a stale entry. The
    572          * scan starts at the element after i.
    573          *
    574          * @param n scan control: <tt>log2(n)</tt> cells are scanned,
    575          * unless a stale entry is found, in which case
    576          * <tt>log2(table.length)-1</tt> additional cells are scanned.
    577          * When called from insertions, this parameter is the number
    578          * of elements, but when from replaceStaleEntry, it is the
    579          * table length. (Note: all this could be changed to be either
    580          * more or less aggressive by weighting n instead of just
    581          * using straight log n. But this version is simple, fast, and
    582          * seems to work well.)
    583          *
    584          * @return true if any stale entries have been removed.
    585          */
    586         private boolean cleanSomeSlots(int i, int n) {
    587             boolean removed = false;
    588             Entry[] tab = table;
    589             int len = tab.length;
    590             do {
    591                 i = nextIndex(i, len);
    592                 Entry e = tab[i];
    593                 if (e != null && e.get() == null) {
    594                     n = len;
    595                     removed = true;
    596                     i = expungeStaleEntry(i);
    597                 }
    598             } while ( (n >>>= 1) != 0);
    599             return removed;
    600         }
    601 
    602         /**
    603          * Re-pack and/or re-size the table. First scan the entire
    604          * table removing stale entries. If this doesn't sufficiently
    605          * shrink the size of the table, double the table size.
    606          */
    607         private void rehash() {
    608             expungeStaleEntries();
    609 
    610             // Use lower threshold for doubling to avoid hysteresis
    611             if (size >= threshold - threshold / 4)
    612                 resize();
    613         }
    614 
    615         /**
    616          * Double the capacity of the table.
    617          */
    618         private void resize() {
    619             Entry[] oldTab = table;
    620             int oldLen = oldTab.length;
    621             int newLen = oldLen * 2;
    622             Entry[] newTab = new Entry[newLen];
    623             int count = 0;
    624 
    625             for (int j = 0; j < oldLen; ++j) {
    626                 Entry e = oldTab[j];
    627                 if (e != null) {
    628                     ThreadLocal k = e.get();
    629                     if (k == null) {
    630                         e.value = null; // Help the GC
    631                     } else {
    632                         int h = k.threadLocalHashCode & (newLen - 1);
    633                         while (newTab[h] != null)
    634                             h = nextIndex(h, newLen);
    635                         newTab[h] = e;
    636                         count++;
    637                     }
    638                 }
    639             }
    640 
    641             setThreshold(newLen);
    642             size = count;
    643             table = newTab;
    644         }
    645 
    646         /**
    647          * Expunge all stale entries in the table.
    648          */
    649         private void expungeStaleEntries() {
    650             Entry[] tab = table;
    651             int len = tab.length;
    652             for (int j = 0; j < len; j++) {
    653                 Entry e = tab[j];
    654                 if (e != null && e.get() == null)
    655                     expungeStaleEntry(j);
    656             }
    657         }
    658     }
    659 }
    View Code

      ThreadLocal对Thread的引用全部通过局部变量完成,而没有一个全局变量。而实际的资源副本则存储在Thread的自身的属性ThreadLocalMap中,这说明,其实ThreadLocal只是关联一个Thread和其资源副本的桥梁,并且实际上Thread和资源副本的生命周期是紧密相连的,的的确确如ThreadLocal所说,在线程被回收的时候,其资源副本也会被回收,虽然ThreadLocal是静态的,但是它既不引用Thread,也不引用ThreadLocalMap。真是精巧的设计!

    五、ThreadLocal作用

         其实ThreadLocal所实现的功能前面已经描述的很清楚了,但是从网上的讨论来看,大家对ThreadLocal所要达成的目的意见却不是很统一,比如博客彻底理解ThreadLocal,具体可以看一下类的注释:

    /**
     * This class provides thread-local variables.  These variables differ from
     * their normal counterparts in that each thread that accesses one (via its
     * <tt>get</tt> or <tt>set</tt> method) has its own, independently initialized
     * copy of the variable.  <tt>ThreadLocal</tt> instances are typically private
     * static fields in classes that wish to associate state with a thread (e.g.,
     * a user ID or Transaction ID).
     */

    从这个注解来看,和TLS(可以参见《Thread Local Storage》)大致是一样的,其实不用去深究其具体目的。个人觉得,ThreadLocal的功能就是为每一个线程提供一个资源副本,当你有这样的需求的时候,就可以使用ThreadLocal去解决问题。

  • 相关阅读:
    ViZDoom深度预测(Depth Prediction)
    刨根问底U3D---从Profile中窥探Unity的内存管理
    关于Android真机调测Profiler
    初探Stage3D(三) 深入研究透视投影矩阵
    初探Stage3D(二) 了解AGAL
    初探Stage3D(一) 3D渲染基础原理
    unity3d优化总结篇
    Unity数据存储路径总结
    CREATE A ENERGY / HEALTH BAR HUD
    CREATE A LOADING SCENE / SPLASH SCREEN
  • 原文地址:https://www.cnblogs.com/lqminn/p/3751206.html
Copyright © 2011-2022 走看看