ThreadLocal

问题一:为什么ThreadLocal中的map对象为什么key是weak类型的?

弱引用概念:弱引用关联的对象只能存活到下一次垃圾回收发生之前。当发生GC时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
使用WeakReference类实现弱引用

好处:::ThreadLocal变更如果置为 null 后(ThreadLocal=null),即使不手工 remove,value也有很高的概率回收。::

当ThreadLocal没有其它强引用时(将ThreadLocal=nul)会被回收;Entry中的key没有其它强引用,key会被回收,但这时,value对象跟Entry数组仍有强引用,不会被回收(value对象会有很高的概率回收);
value对象会有很高的概率回收是指:当前线程再调用用其它ThreadLocal 的get、set方法时会回收,如果在将ThreadLocal=null之前,调用remove方法Entry整个(包含value)会立即回收。
如果key设为强引用,Entry数组->Entry-key(ThreadLocal) ,强引用会一直存在,即使ThreadLocal=null,由于线程存在,强引用一直存在,只能手工删除。( Thread. threadLocals变更为ThreadLocal.ThreadLocalMap)

引申问题:为什么说显式地进行remove是个很好的编码习惯,这样是不会引起内存泄漏?

  1. 调用remove方法,会调用expungeStaleEntry 方法立刻从当前slot开始清理,一定会清理当前对象。
  2. 如果没有调用remove方法,只能说如果对应线程之后调用ThreadLocal的get和set方法都有很高的概率会顺便清理掉无效对象,断开value强引用,从而value对象被收集器回收。但如果线程一直存在,又没有再调用set、get方法,对象就无法被回收,就会发生内存泄露

问题二:key存的是什么?
答:key存的是ThreadLocal,为弱引用

Entry数组->Entry-key(ThreadLocal)

static class Entry extends WeakReference> {
    Object value;
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

问题三:ThreadLocal中的map对象是map吗?
答:是一个环状数组,用开放录址法解决冲突。

总结:

  1. Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals
  2. ThreadLocalMap是环状数组实现的:
    private Entry[] table;是一个数组,数组上一个下一个通过一标i 加1 减一查找;可以认为是一个环。
  3. 开式寻址法的较简单暴力:hash值后,判断当前hash的值的下标是否为空,不为空就加1查找直至找到为空的值为止。

ThreadLocal为什么要使用弱引用和内存泄露问题(引用)
Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key.每个key都弱引用指向threadlocal.假如每个key都强引用指向threadlocal,也就是上图虚线那里是个强引用,那么这个threadlocal就会因为和entry存在强引用无法被回收!造成内存泄漏 ,除非线程结束,线程被回收了,map也跟着回收。
虽然上述的弱引用解决了key,也就是线程的ThreadLocal能及时被回收,但是value却依然存在内存泄漏的问题。当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal将会被gc回收.map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露,因为存在一条从current thread连接过来的强引用.只有当前thread结束以后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将全部被GC回收.所以当线程的某个localThread使用完了,马上调用threadlocal的remove方法,就不会发生这种情况了。
另外其实只要这个线程对象及时被gc回收,这个内存泄露问题影响不大,但在threadLocal设为null到线程结束中间这段时间不会被回收的,就发生了我们认为的内存泄露。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用,就可能出现内存泄露。

ThreadLocal使用

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. 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).
For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.

   import java.util.concurrent.atomic.AtomicInteger;
   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 threadId =
           new ThreadLocal() {
               @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();
       }
   }

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

TreadLocal.png

使用ThreadLocal的时机:

1、满足应该场景时,可以减少方法参数传递,优化代码;
2、当能加快生成对象速度时(不用每次都生成一个对象)。

原理

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

ThreadLocalMap中 private Entry[] table;主要存的是一个数组,数组上一个下一个通过一标i 加1 减一查找;可以认为是一个环。

由于ThreadLocalMap使用线性探测法来解决散列冲突,所以实际上Entry[]数组在程序逻辑上是作为一个环形存在的。
关于开放寻址、线性探测等内容,可以参考网上资料或者TAOCP( 《计算机程序设计艺术》 )第三卷的6.4章节。
至此,我们已经可以大致勾勒出ThreadLocalMap的内部存储结构。下面是我绘制的示意图。虚线表示弱引用,实线表示强引用。

开式录址法的较简单暴力:hash值后,判断当前hash的值的下标是否为空,不为空就加1查找直至找到为空的值为止。

开式录址法1.png
开式录址法2.png

源码解析

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

expungeStaleEntry方法

这个函数是ThreadLocal中核心清理函数,它做的事情很简单:
就是从staleSlot开始遍历,将无效(弱引用指向对象被回收)清理,即对应entry中的value置为null,将指向这个entry的table[i]置为null,直到扫到空entry。

 /**
         * 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();
                if (k == null) {
                    e.value = null;
                    tab[i] = null;
                    size--;
                } else {
                    int h = k.threadLocalHashCode & (len - 1);
//如果当前entry的hash值不与当前entriy所处table的下标i相同,表明此entry的key的hash后生成的下标是存在冲突的。
//将当前entry移到之前为空的位置,至到找到为空的tab[i](表明未到找该key).
                    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;
        }

引用

Java面试必问,ThreadLocal终极篇
ThreadLocal源码解读 - 活在夢裡 - 博客园
ThreadLocal 内部实现、应用场景和内存泄漏 - 淡淡的倔强的博客 - CSDN博客

你可能感兴趣的:(ThreadLocal)