你的位置:首页 > 操作系统

[操作系统]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.2360679774997898System.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();    }  }