ThreadLocal:线程内通信

参考资料:https://www.cnblogs.com/micrari/p/6790229.html

参考资料:https://blog.csdn.net/anlian523/article/details/105523826

它不是用来解决多线程环境下的共享变量问题,而是用来提供线程内部的共享变量,在多线程环境下,可以保证各个线程之间的变量互相隔离、相互独立

ThreadLocal是Java里一种特殊的变量。每个线程都有一个ThreadLocal就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了。如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。首先,通过复用减少了代价高昂的对象的创建个数。其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。

底层原理:每一个线程其内部维护了一个ThreadLocalMap映射表,key存储当前的ThreadLocal,value是存储我们的业务对象,仅仅是一个工具。

为什么ThreadLocal可以实现线程隔离的线程私有变量:因为它把这个Map对象直接作为了Thread对象的成员,这样,每个运行的线程都对应到一个唯一的Thread对象,而每个Thread对象都保存着各自的ThreadLocal.ThreadLocalMap类型的成员变量。

为什么要弱引用:因为如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

 

创建ThreadLocal对象的两种方式:

直接new:ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {protected Integer initialValue() {return 1;}};

调静态方法:ThreadLocal<Integer> integerThreadLocal = ThreadLocal.withInitial(() -> 1);

public T get() { //获取与当前线程关联的ThreadLocal值

        ThreadLocalMap map = Thread.currentThread().threadLocals; //每个thread底层都维护了一个ThreadLocalMap对象

        if (map != null) { //已经被初始化过了

//entry继承WeakReference,下一次gc有可能被回收

//key并不是ThreadLocal本身,而是它的一个弱引用
//Entry不是弱可达的,不会被JVM清理。Entry是一个WeakReference对象,弱可达的是放在里面的ThreadLocal对象。

            ThreadLocalMap.Entry e = map.getEntry(this); //this为当前的ThreadLocal对象

            if (e != null) {

                T result = (T)e.value;

                return result;

            }

        }

        return setInitialValue();

}



private Entry getEntry(ThreadLocal<?> key) {

//此i是初始下标,因为存在hash冲突,后续需要采用线性探测法去找

int i = key.threadLocalHashCode & (table.length - 1); //计算key在table上的下标

   Entry e = table[i];

   if (e != null && e.get() == key)// 对应的entry存在且未失效且弱引用指向的ThreadLocal就是key,则命中返回

return e;

//e可能为null:

//e不为null,但是key没对应上

// 因为用的是线性探测,所以往后找还是有可能能够找到目标Entry的。

   return getEntryAfterMiss(key, i, e);

}



/*调用getEntry未直接命中的时候调用此方法 */

private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {

Entry[] tab = table;

int len = tab.length;

// 基于线性探测法不断向后探测直到遇到空entry。不断遍历table中的entry

while (e != null) {

ThreadLocal<?> k = e.get();

if (k == key) return e;

 if (k == null) //通过弱引用的key被回收了

     expungeStaleEntry(i); //删除过期的entry,value不会被回收同时又不能被访问到,导致内存泄漏了

else

i = nextIndex(i, len); //模拟环形:下一个,如果到了最后一个,就从第一个开始

e = tab[i];

}

//如果考虑了哈希冲突后,开放寻址还是不能找到entry,那么说明该key确实不在map中

return null;

}



private int expungeStaleEntry(int staleSlot) { //删除过期的entry,key被gc回收了,导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏
    Entry[] tab = table;
    int len = tab.length;

    // 因为entry对应的ThreadLocal已经被回收,value设为null,显式断开强引用 

tab[staleSlot].value = null;

// 显式设置该entry为null,以便垃圾回收 

tab[staleSlot] = null;

size--;


    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;
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}



public void remove() { //将与当前线程关联的ThreadLocal值删除

   ThreadLocalMap m = getMap(Thread.currentThread());

   if (m != null)

       m.remove(this);

}



private T setInitialValue() {
     T value = initialValue(); //初始值
     Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null)
         map.set(this, value); //更新value
     else
         t.threadLocals = new ThreadLocalMap(this, value); //初始化threadLocals

   return value;
}



ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {

        table = new Entry[16]; //初始化容量=16

        int i = firstKey.threadLocalHashCode & 15; //计算索引位置

        table[i] = new Entry(firstKey, firstValue);//entry extends WeakReference 如果没有任何引用指向,下一次gc被回收

        size = 1;

        setThreshold(INITIAL_CAPACITY);

    }

猜你喜欢

转载自blog.csdn.net/qq_33436466/article/details/107716408