探究ThreaLocal

前言

    ThreadLocal提供一个(只有一个)线程的局部变量,为了确保多线程环境下,线程的安全性。其实可以这样理解,ThreadLocal其实就是一个普通类,它声明的对象有明确的作用范围,这个范围就是用ThreadLocal去声明对象的线程,当然每个线程可以有多个ThreadLocal变量,这些ThreadLocal变量被保存在ThreadLocalMap中,这个ThreadLocalMap其实相当于一个key-value结构的Map,结构大概是这样ThreadLocalMap

例子

/**
 * @author: ggp
 * @Date: 2019/7/2 20:56
 * @Description:
 */
public class ThreadLocalTest {
    public static void main(String[] args) {
       ThreadLocal t = new ThreadLocal<>();
       Thread thread1 = new Thread(new Runnable() {
           @Override
           public void run() {

              for(int i =0; i<100;i++){
                  if(null == t.get()){
                      t.set(new Number());
                  }
                  t.get().setVal(i);
                  System.out.println(Thread.currentThread().getName()+">>>>>>>>>>"+t.get().getVal());
              }
           }
       });
       thread1.start();
       Thread thread2 = new Thread(new Runnable() {
           @Override
           public void run() {
               for(int i=1000;i<1100;i++){
                   if(null == t.get()){
                       t.set(new Number());
                   }
                   t.get().setVal(i);
                   System.out.println(Thread.currentThread().getName()+">>>>>>>>>>"+t.get().getVal());
               }
           }
       });
       thread2.start();
    }
}
class Number{
    int val = 2;

    public int getVal() {
        return val;
    }

    public void setVal(int val) {
        this.val = val;
    }
}

运行结果

探究ThreaLocal_第1张图片

可以看到两个线程互不干扰,确保了多线程之间的线程安全。

源码分析

threadLocal.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);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

   /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

   首先先获取到当前线程t,然后拿到线程的局部变量表ThreadLocalMap

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

 跟踪源码可以看出ThreadLocalMap是 ThreadLocal的一个静态内部类,而Entry是ThreadLocalMap的基本组成单位

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> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }

可以看到作者的注释,这个entry实现了弱引用的接口,而这个弱引用指向hashmap中的key值,所以当key值为null时,GC的时候就会回收key值,但是value并不是弱引用,所以不会被回收。entry没法被调用却也占着内存,就会造成内存泄露,直到当前线程结束,线程被回收,内存才会被完全释放。但是如果这个场景发生在线程池,那么内存泄露就会一直存在,直到应用结束。

但是,人家jdk早就有了解决方法。。。。。。。。。。

在entry的get和set方法中会做一个判断来清楚这些隐患

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

         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;
        }
        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();
                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;
        }

以get为例,计算hash值,当拿到的entry中的key为空的时候进入 getEntryAfterMiss()中的expungeStaleEntry()方法中,通过源码可以看出,首先是将value置空,然后把整个entry置空,最后重新hash。

你可能感兴趣的:(源码阅读之路)