美文网首页
ThreadLocal详解

ThreadLocal详解

作者: 星空怎样 | 来源:发表于2020-05-28 17:51 被阅读0次

[toc]

作用

ThreadLocal提供了线程内的局部变量,相比较锁,他是空间换时间的思想,使得每个线程访问属于自己的独立副本。

这个类给线程提供了一个本地变量,这个变量是该线程自己拥有的,在该线程存活的ThreadLocal实例访问的时候,保存了这个变量副本的引用,如果当线程消失的时候,所有的本地实例都会被GC建议使用ThreadLocal最好使用private static修饰的成员。

注:当线程中某个ThreadLocal被GC掉(线程没有消亡)此时Entry中的key值将称为null,而value值还存在,知道线程消亡之前,Entry中的value都无法被gc回收,这就可能造成内存泄漏。为了解决这个问题,源码中set、get、remove、rehash方法执行时都会清除这些key为null的entry,当我们调用set、get方法时,都有可能将Entry的key为null的对象给清除掉(前提是触发了key是null的操作,如果没有触发这个操作也是不会清除key是null的情况),但是还会出现内存泄漏问题

为了避免内存泄漏可以有以下两种方式:

  • 使用完手动执行remove()
  • 将ThreadLocl定义为private static,这样可以随着线程一起消亡。

结构

image

每个Thread维护一个TreadLocalMap,ThreadLocalMap中存储的是一个Entry[]tab数组,Entry中的key是ThreadLocal,value是ThreadLocal中设置的值。

Thread与ThreadLocal的关系

ThreadLocal的set方法:

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程对应的ThreadLoclMap
        ThreadLocalMap map = getMap(t);
        //如果map不是null那么就set值,如果是null那么先创建,然后在设置值
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    //获取当前线程的ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    //给Thread创建一个ThreadLocalMap
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    //下面是Thread对ThreadLocalMap的定义
    ThreadLocal.ThreadLocalMap threadLocals = null;

其实Thread类中有一个ThreadLocalMap为null的变量,如果Thread返回引用是null,就调用createMap方法,这就为Thread创建了一个能保存本地线程的map,当第一次使用ThreadLocl的时候,通过getMap()得到的ThreadMap必定是null所以需要调用createMap。

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    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);
        }
        //设置map扩容的阈值,当Entry[]中元素达到这个阈值时候开始扩容,阈值为Entry[]长度的2/3
    private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

当达到最大长度的2/3时候就会扩容

可能会出现的问题

ThreadLocal中的Entry的key值是弱引用,代表他很快被GC掉。
弱引用:不会影响对象的GC(当对象只有弱引用存在的时候,只要GC就会被回收掉)
个人认为:如果是强引用,ThreadLocal不在使用时,GC时候ThreadLocalMap中还存在强引用,导致无法被GC回收,如果弱引用ThreadLocalMap就不算强引用了,就可以被GC回收,map的后续操作中,也会逐渐把对应的过时条目(key==null)清理出去,避免内存内存泄漏,Entry的定义如下:

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

ThreadLocalMap使用弱引用key,如果一个ThreadLocal没有外部强引用,那么系统gc的时候这个ThreadLocal必然会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果线程不结束,这些key为null的Entry的value就会一直存在一条强引用链,就会造成内存泄漏。

ThreadLocal之间是如何区分的

//每一个对象都有一个hashCode来标志自己的唯一性
private final int threadLocalHashCode=nextHashCode();
//下一个要给出的哈希码。原子更新。从零开始,原子类,保证线程安全,保证每一个hashCode唯一,并且是静态的
private static AtomicInteger nextHashCode=new AtomicInteger();
/**
 * The difference between successively generated hash codes - turns
 * implicit sequential thread-local IDs into near-optimally spread
 * multiplicative hash values for power-of-two-sized tables.
 * hash增量,每次获得hash之后加上这个数
 */
private static final int HASH_INCREMENT=0x61c88647;

/**
* 返回下一个hashCode
*/
private static int nextHashCode(){
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

比如第一个ThreadLocal的hashCode就是0,那么在定义一个他的hashCode的基础加上HASH_INCREMENT,这样map中他们的hashCode不一样,但是这个时候虽然hashCode不一样,但是计算下标有可能一样,造成hash处重复,在ThreadLocalMap里面解决hash冲突是线性探查法来解决,当i下标有值的时候则找到i+1处,然后依次往下推。

总结

调用set或者get时候,ThreadLocal会有可能触发清除key为null的值,使用线程池的时候,我们应该在线程使用完ThreadLocal的时候自觉的调用remove方法清空Entry。
废弃的ThreadLocal所绑定的对象引用,会在以下情况被清理:

  • Thread结束时
  • 当Thread的ThreadLocalMap的threshold超过最大值(也就是触发rehash扩容时候)
  • ThreadLoclMap中存放一个ThreadLocal,hash算法之后获取的Entry中的key为null时
  • 手动通过ThreadLocal的remove方法或者set(null)

相关文章

  • ThreadLocal和InheritableThreadLoc

    ThreadLocal详解 - 简书 InheritableThreadLocal详解 - 简书 ThreadLo...

  • InheritableThreadLocal详解

    1、简介 在上一篇 ThreadLocal详解 中,我们详细介绍了ThreadLocal原理及设计,从源码层面上分...

  • ThreadLocal 详解

    ThreadLocal 详解 1. 前言 ThreadLocal是java线程中的局部变量,变量作用域仅在当前线程...

  • ThreadLocal详解

    1、简介 ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Th...

  • ThreadLocal详解

    ThreadLocal之前我认为就是内部维护了一个ThreadLocalMap其中key为当前线程(Thread....

  • ThreadLocal详解

    介绍 顾名思义这个类提供线程局部变量每个线程(通过其get或set方法)都有自己独立初始化的变量副本 Thread...

  • ThreadLocal 详解

    ThreadLocal,我们一般称之为线程的局部变量,或者是线程的本地变量。很多人认为他与多线程的同步机制相关,其...

  • ThreadLocal详解

    ThreadLocal在java.lang包中,其主要作用是提供一个和线程绑定的变量环境,即通过ThreadLoc...

  • ThreadLocal详解

    ThreadLocal说明 ThreadLocal是一个线程内部的数据存储类,使用它来保存数据,只有当前的线程才可...

  • ThreadLocal详解

    ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。 这个玩意有什么用处,或者说为什么要有这么一个...

网友评论

      本文标题:ThreadLocal详解

      本文链接:https://www.haomeiwen.com/subject/nzwaahtx.html