ThreadLocal

ThreadLocal

  • ThreadLocal也叫线程本地变量、线程局部变量

    • 其作用域覆盖线程,而不是某个任务
    • 其自然的生命周期与线程的生命周期相同(但在JDK实现中比线程的生命周期更短,减少了内存泄漏的可能性)
  • ThreadLocal代表了一种线程与任务剥离的思想,从而达到线程封闭的目的,帮助我们设计出更健康的线程安全类

  • ThreadLocal适用于每个线程需要自己独立的实力且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

使用实例

public class MyThreadLocal {
    private static final ThreadPoolExecutor EXECUTOR_SERVICE = new ThreadPoolExecutor(100,120,60, TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(1000),
            new ThreadFactoryBuilder().setNameFormat("thread-pool-%d").build(),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public static void main(String[] args) {
        final ResultData resultData = new ResultData();
        EXECUTOR_SERVICE.execute(()->resultData.getNum());
        EXECUTOR_SERVICE.execute(()->resultData.getNum());
        EXECUTOR_SERVICE.execute(()->resultData.getNum());

        final NoThreadLocalResultData noThreadLocalResultData = new NoThreadLocalResultData();
        EXECUTOR_SERVICE.execute(()->noThreadLocalResultData.getNum());
        EXECUTOR_SERVICE.execute(()->noThreadLocalResultData.getNum());
        EXECUTOR_SERVICE.execute(()->noThreadLocalResultData.getNum());
    }

}
class NoThreadLocalResultData{
    public static Integer count=0;
    public Integer getNum() {
        count = count + 1;
        System.out.println("noThreadLocal:"+count);
        return count;
    }
}
class ResultData{
    // 生成序列号共享变量
    public static Integer count=0;
    private static ThreadLocal<Integer> threadLocal=new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };
    public Integer getNum() {
        int count = threadLocal.get() + 1;
        threadLocal.set(count);
        System.out.println("threadLocal:"+count);
        return count;
    }
}
  • 运行结果
threadLocal:1
threadLocal:1
threadLocal:1
noThreadLocal:1
noThreadLocal:2
noThreadLocal:3

源码分析

  • ThreadLocal的get方法
public T get() {
    Thread t = Thread.currentThread();
    //ThreadLocalMap是ThreadLocal中的内部类
    //该map实际保存在一个Thread实例中
    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();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

ThreadLocal.ThreadLocalMap threadLocals = null;
  • 分析ThreadLocalMap
    • ThreadLocalMap没有实现map接口。内部通过Entry存储数据。
    • Entry继承了一个ThreadLocal泛型的WeakReference引用
static class ThreadLocalMap {

    //Entry里面存储ThreadLocal类型数据
    //WeakReference是为了方便垃圾回收
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

    private static final int INITIAL_CAPACITY = 16;

    //Entry数组来存放对象实例和变量的关系,并且实例对象为key
    private Entry[] table;

    private int size = 0;

    private int threshold; // Default to 0

    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }

    private static int nextIndex(int i, int len) {
        return ((i + 1 < len) ? i + 1 : 0);
    }

    private static int prevIndex(int i, int len) {
        return ((i - 1 >= 0) ? i - 1 : len - 1);
    }
  • set方法
private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    //获取hash值,用于数组中的下标
    int i = key.threadLocalHashCode & (len-1);

    //如果数组该位置有对象则进入
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        //相等则覆盖旧值
        if (k == key) {
            e.value = value;
            return;
        }
        //k=null说明k的对象实例已经被回收了,需要替换这个位置的key和value
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //如果该位置没有对象,则创建新Entry对象
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}
  • 问题:如果任务对象结束而线程实例仍然存在(常见于线程池的使用中,需要复用线程实例),那么仍然会发生内存泄漏

  • 使用WeakReference减少内存泄漏

    • 对于弱引用WeakReference,当一个对象仅仅被弱引用指向,而没有任何其他强引用StrongReference指向的时候,下一次GC运行的时候就会回收这个对象
    • 在ThreadLocal变量的使用过程中,由于只有任务对象拥有ThreadLocal变量的强引用(最简单的情况),所以任务对象被回收后,就没有强引用指向ThreadLocal变量,ThreadLocal变量也就会被回收
  • 问题:虽然使用了弱引用,但是ThreadLocalMap中仍然会发生内存泄漏。ThreadLocal变量只是Entry中的key,所以Entry中的key虽然被回收了,Entry本身却仍然被引用。

    • 在这种情况下,ThreadLocalMap在它的getEntry、set、remove等方法后都会主动清除ThreadLocalMap中key为null的Entry。
  • 在做了上述两步操作后,已经可以大大减少内存泄漏的可能,但如果我们声明ThreadLocal变量后,再也没有调用过上述方法,仍然会发生内存泄漏。不过,现实世界中线程池的容量总是有限的,所以这部分泄漏的内存并不会无限增长;另一方面,目前线程池都设置了过期时间,所以一旦线程空闲时间较长,线程就会被回收。所以,这种情况下存在的内存泄漏是可以允许的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值