ThreadLocal的工作原理分析及应用

  ThreadLocal 用在变量跟着线程变化时。Spring事物中数据库的连接就是使用ThreadLocal存放的。

一.ThreadLocal的官方说明

JDK ThreadLocal的解释说明:

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

  大致意思是:ThreadLocal提供线程本地变量。这些变量不同于他们正常的副本,他们每一个线程都是(通过get或者set方法)访问自己独立初始化的变量副本。ThreadLocal实例通常是希望将线程的状态与类中私有静态属性关联。
jdk ThreadLocal的垃圾回收说明:

Each thread holds an implicit reference to its copy of a thread-local
 variable as long as the thread is alive and the {@code ThreadLocal}
 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).

  大致意思是:只要线程是活动的,并且可以访问{@code ThreadLocal} 实例,则每个线程都对其线程局部变量的副本持有隐式引用。线程消失后,所有其线程本地实例的副本都将进行垃圾回收(除非存在对这些副本的其他引用)。

从上面JDK的官方说明可以知道
  1.每个线程都有自己独立的线程变量副本,且各自的副本只能所属的线程调用;
  2.每个线程副本之间互不影响,所以也就不存在多线程共享问题。
  3.ThreadLocal关联的变量通常被 private static修饰;

二.ThreadLocal的使用方法

//创建一个ThreadLocal 并给以初始化值
    private static ThreadLocal<Integer> integerThreadLocal = 
    	new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };
    public static void main(String[] args) {
         Thread[] thread = new Thread[3];
        for (int i = 0; i < thread.length; i++) {
            thread[i] = new Thread(new LocalThread(1));
        }
        for (int i = 0; i < thread.length; i++) {
            thread[i].start();
        }
    }
    /**
     * @author charles
     * @date 2020/5/18 18:34
     * @desc 类说明:测试线程,线程的工作是将ThreadLocal变量的值变化,
     * 并写回,看看线程之间是否会互相影响
     */
    static class LocalThread implements Runnable{
        private int id;
        public LocalThread(int id){
            this.id = id;
        }
        @Override
        public void run() {
            Integer s = integerThreadLocal.get();
            s = s+id;
            integerThreadLocal.set(s);
            System.out.println(Thread.currentThread().getName()
                    +":"+ integerThreadLocal.get());
        }
    }

执行结果:
在这里插入图片描述
  三个线程都对integerThreadLocal 进行了set存值操作,而每一个integerThreadLocal 的值都只加了 1 ,说明他们之间是相互不影响的!

三.ThreadLocal的常用方法及源码分析

1.get() 方法:获取变量副本中的值

   /**
     * 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); //获取当前线程存放的ThreadLocalMap 对象
        if (map != null) {  //判断当前 ThreadLocalMap 是否有本地变量副本,
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue(); //初始化值,在调用get()时才会初始化值
    }

   在调用 map.getEntry(this)时,获取到将返回获取到的值。如果获取不到,则会清空ThreadLocalMap中 key为空的Entry 对象。使GC时该对象能被顺利回收,防止内存泄漏。

 /**
         * 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;
        }
        ......
        
		/**
         * Expunge a stale entry by rehashing any possibly colliding entries
         * lying between staleSlot and the next null slot.  This also expunges
         * any other stale entries encountered before the trailing null.  See
         * Knuth, Section 6.4
         *
         * @param staleSlot index of slot known to have null key
         * @return the index of the next null slot after staleSlot
         * (all between staleSlot and this slot will have been checked
         * for expunging).
         */
        private int expungeStaleEntry(int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;

            // expunge entry at staleSlot
            tab[staleSlot].value = null;
            tab[staleSlot] = null;
            size--;

            // Rehash until we encounter null
            Entry e;
            int i;
            for (i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();
                
                //在此处若 key为null时,把值和当前Entry对象赋值为null,等待GC回收
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
                    if (h != i) {
                        tab[i] = null;

                        // Unlike Knuth 6.4 Algorithm R, we must scan until
                        // null because multiple entries could have been stale.
                        while (tab[h] != null)
                            h = nextIndex(h, len);
                        tab[h] = e;
                    }
                }
            }
            return i;
        }

2.set() 方法:把变量存到变量副本中

/**
 * 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);  //获取当前线程存放的ThreadLocalMap 对象
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

3.remove()方法

  将当前线程局部变量的值删除,目的是为了减少内存的占用。该方法是JDK1.5新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将会自动被垃圾回收。所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存的回收速度。

/**
 * 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 = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

4.initialValue()方法

   返回该变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法。在线程第一次调用get()方法时才会执行,并且只会执行一次。ThreadLocal中的缺省实现直接返回一个null;

5.ThreadLocalMap 对象

  ThreadLocal中定义的一个map,用于存储线程中的变量副本。ThreadLocal 为key,变量值为value。并且Entry继承了WeakReference 便于 GC垃圾回收

  /**
     * 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 initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

四. ThreadLocal与Thread的关系

ThreadLocal的工作原理分析及应用_第1张图片
   每个线程中都包含一个ThreadLocalMap对象,而每个ThreadLocalMap中有包含一个Entry数组。而ThreaLocal与Entry之间是存在弱引用关系。一个ThreadLocal对应一个Entry对象。而每个线程都拥有自己专属的ThreadLocal变量,从而使ThreadLocal中的变量互不干扰,实现线程隔离。

你可能感兴趣的:(java,多线程并发编程)