JDK ThreadLocal源码阅读

概述

介绍了JDK源码中,ThreadLocal对象的细节点,以加深对其内部原理的认识。夯实读者Java基础知识。\
ThreadLocal是线程变量,是一个以自身为键,线程数据为值的类map存储结构。也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。
它其实是在每个线程中都创建一个副本数据,这样每个线程在执行时,可以同时使用自己的变量,并且它们之间互不影响,彼此不存在多线程数据共享的问题。

细节分析

  • 基本介绍其使用原因

    通常我们在执行多线程并发的时候,通过使用加锁的方式来保证线程之间的数据共享不会出错。然而,加锁后,使得线程之间的执行是顺序执行,即一个线程
    执行时,必须首先获得锁,若此时锁被其他线程使用,则其必须阻塞等待。这也就是人们常说的:在多线程访问的时候,为了解决线程安全问题,
    使用 synchronized 关键字来实现线程同步的可以解决多线程并发访问的安全问题,但是在这种解决方案存在有性能问题。这种情况下,多个线程使用的
    是同一套变量数据,必须保证线程之间的访问不受到影响。这样使用的结果就是,最终所有线程执行完所花费的时间比较长。

    ThreadLocal可以解决多线程之间花费时间长的问题。因为该类会在每个线程执行时,会为每个线程都创建一个变量副本。每个线程都拥有了自己独立的副本数据集,
    竞争条件被彻底消除了,此时就可以不用使用synchronized进行线程同步,同时也能最大限度的使用系统资源,由CPU调度并发执行。各个线程之间对数据的
    增删改都不会干扰其他的线程使用。

    相比于多线程之间加synchronized同步锁,减少数据的执行时间,但是此种方式,需要更多的存储空间。这也就是以空间换取时间的思想。

  • 源码分析

    1.构造函数-创建实例

     public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
         return new SuppliedThreadLocal<>(supplier);
     }
    
     /**
      * Creates a thread local variable.
      * @see #withInitial(java.util.function.Supplier)
      */
     public ThreadLocal() {
     }

    可以通过无参数构造函数创建,也可以使用静态函数withInitial获取一个SuppliedThreadLocal实例。

    2.设置数据和获取数据源码

    /**
    * 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);
      else
          createMap(t, value);
    }
    /**
    * 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);
      if (map != null) {
          ThreadLocalMap.Entry e = map.getEntry(this);
          if (e != null) {
              @SuppressWarnings("unchecked")
              T result = (T)e.value;
              return result;
          }
      }
      return setInitialValue();
    }
    
    public void remove() {
      ThreadLocalMap m = getMap(Thread.currentThread());
      if (m != null)
          m.remove(this);
    }

    从中可知其数据保存在ThreadLocalMap对象中,若map存在时直接保存,不存在时先创建一个,获取时,若map不存在,则获取默认数据。
    get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本,
    initialValue()是一个protected方法,一般是用来在使用时进行重写的,它是一个延迟加载方法。具体如下:

    void createMap(Thread t, T firstValue) {
      t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    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;
    }
    
    ThreadLocalMap getMap(Thread t) {
      return t.threadLocals;
    }

    通过源码分析可以看出,通过获取和设置 Thread内的 threadLocals变量,而这个变量的类型就是 ThreadLocalMap,
    这样进一步验证了上文中的观点:每个线程都有自己独立的ThreadLocalMap对象。

    3.数据存储结构(ThreadLocalMap)

    其内部是以数组作为存储结构private Entry[] table,其中Entry 继承了弱引用,具体如下:

    static class Entry extends WeakReference<ThreadLocal<?>> {
       /** The value associated with this ThreadLocal. */
       Object value;
    
       Entry(ThreadLocal<?> k, Object v) {
           super(k);
           value = v;
       }
    }
    
    /**
    * 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);
    }
    /**
    * The initial capacity -- MUST be a power of two.
    */
    private static final int INITIAL_CAPACITY = 16;

    从代码中可以看出,内部数组的初始容量为16,根据需要会扩容,每次扩容为上一次的2倍:

    /**
    * Double the capacity of the table.
    */
    private void resize() {
       Entry[] oldTab = table;
       int oldLen = oldTab.length;
       int newLen = oldLen * 2;
       Entry[] newTab = new Entry[newLen];
       int count = 0;
    
       for (int j = 0; j < oldLen; ++j) {
           Entry e = oldTab[j];
           if (e != null) {
               ThreadLocal<?> k = e.get();
               if (k == null) {
                   e.value = null; // Help the GC
               } else {
                   int h = k.threadLocalHashCode & (newLen - 1);
                   while (newTab[h] != null)
                       h = nextIndex(h, newLen);
                   newTab[h] = e;
                   count++;
               }
           }
       }
    
       setThreshold(newLen);
       size = count;
       table = newTab;
    }
    /**
    * The next size value at which to resize.
    */
    private int threshold; // Default to 0
    
    /**
    * Set the resize threshold to maintain at worst a 2/3 load factor.
    */
    private void setThreshold(int len) {
       threshold = len * 2 / 3;
    }
    
    /**
    * Re-pack and/or re-size the table. First scan the entire
    * table removing stale entries. If this doesn't sufficiently
    * shrink the size of the table, double the table size.
    */
    private void rehash() {
       expungeStaleEntries();
    
       // Use lower threshold for doubling to avoid hysteresis
       if (size >= threshold - threshold / 4)
           resize();
    }

    根据threshold和load factor决定关系决定扩容情况。load factor是2/3,扩容的条件为:size>= threshold - threshold/4。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;
           }
       }
    }
    

总结

  • 能够以空间换取时间,解决多线程同步造成性能差的影响。但是两者的应用领域不同,同步机制是为了对相同资源的并发访问,是为了多个线程之间进行通信的有效方式;
    而ThreadLocal是隔离多个线程的数据共享,从根本上就不在多个线程之间共享资源(变量),这样当然不需要对多个线程进行同步了。
  • 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
  • ThreadLocal也有它的缺点,就是造成内存泄露。因为置为null的threadlocal对应的Object Value无法及时回收。(可以定时手动调用ThreadLocalMap的remove方法清理)
  • threadLocal中不存入大量数据时候,GC可以自动回收,所以正常情况下,不会引起较大的内存泄漏的问题。
  • threadLocal应用前景广泛,例如,数据库连接和Session会话管理的方面。

实例

未完待续。。。

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页