ThreadLocal使用方式,源码分析,内存泄漏

ThreadLocal是干什么的?

    用来隔离线程之间参数对象的,简单来说就是每个线程在使用的时候,都获取到每个线程上所绑定参数,举个例子,web开发项目当中,每个用户登录进来,访问网站,都会有个新线程创建,我们在每个线程中绑定用户信息,这样就可以在任何时刻获取到当前用户的信息,避免了参数传递。

先来看一个简单的例子

public class ThreadLocalTest1 {
    ThreadLocal<Long> longLocal = ThreadLocal.withInitial(()->
            Thread.currentThread().getId()
    );
    ThreadLocal<String> stringLocal = ThreadLocal.withInitial(() -> Thread.currentThread().getName());


    public void set() {
        longLocal.set(Thread.currentThread().getId());
        stringLocal.set(Thread.currentThread().getName());
    }

    public long getLong() {
        return longLocal.get();
    }

    public String getString() {
        return stringLocal.get();
    }

    public static void main(String[] args) throws Exception{
        final ThreadLocalTest1 test = new ThreadLocalTest1();


     //   test.set(); 如果没有重写initValue方法会导致抛出异常
        System.out.println(test.getLong());
        System.out.println(test.getString());


        Thread thread1 = new Thread(){
            public void run() {
             //   test.set();
                System.out.println(test.getLong());
                System.out.println(test.getString());
            };
        };
        thread1.start();
        thread1.join();

        System.out.println(test.getLong());
        System.out.println(test.getString());
    }
}

执行结果:

1
main
11
Thread-0
1
main

我们在每个线程当中都保存了各自的线程id和线程名称,

ThreadLocal 当中get和set方法的实现

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

1、获取到当前线程

2、从当前线程中查询到线程内部的变量ThreadLocalMap

3、如果当前线程中维护的ThreadLocalMap不为null , 查询map中,key为ThreadLocal的value值

4、如果当前线程ThreadLocalMap还没初始化,执行setInitialValue

 private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

1、获取到ThreadLocal中initialValue值

2、判断当前线程中是否包含ThreadLocalMap,

3、如果当前线程中含有ThreadLocalMap, 放入进去当前ThreadLocal, 和ThreadLocal中initialValue值,

4、如果没有ThreadLocalMap, 创建一个Map,

 void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

这段代码很简单就是创建一个新的ThreadLocalMap,将当前ThreadLocal及ThreadLocal 中initialValue的值传递进去

总结起来就是:

每个线程都含有自己的ThreadLocalMap<ThreadLocal,Object>, Obejct的值,就是我们需要为每个线程所保存的一个数据,如果没有重写ThreadLocal当中的initialValue方法,需要调用ThreadLocal的set方法将数据保存进去,否则会抛出空指针异常,如果重写了initialValue方法,在get时,会将initialValue保存进去

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

1、获取到当前线程中保存的ThreadLocalMap,

2、如果ThreadLocalMap不为空,将当前ThreadLocal和value放置进去

3、如果为null, 创建一个新的ThreadLocalMap

如果ThreadLocal当中value是一个共总对象,那么每个线程之间,关于这个对象的保存并不是隔离开的

如下例子:

public class ThreaLocalTest2 {
    private static A a  = new A();
    private static final ThreadLocal<A> threadLocal = ThreadLocal.withInitial(()->a);

    public static void main(String[] args) {
        Thread[] threads = new Thread[5];
        for (int i = 0; i <  threads.length; i++) {
            threads[i] = new Thread(() ->{
                threadLocal.get().setNumber(threadLocal.get().getNumber() + 5);
                System.out.println(Thread.currentThread().getName() + ":"
                 + threadLocal.get().getNumber());
            },"Thread-"+i);
        }
        for(Thread thread : threads) {
            thread.start();
        }
    }
}
class A{
    private int number = 0;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

执行结果:

Thread-0:5
Thread-2:15
Thread-1:10
Thread-4:20
Thread-3:25

这个结果显然并不是我们想要的,原因就在于他们访问的是同一个对象,如果将ThreadLocal.withInitial(() ->new A()),就可以保证每个线程访问的数据之间是隔离开来的

ThreadLocal内存泄漏

    先来看下ThreadLocal当中ThreadLocalMap的类

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

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

ThreadLocalMap当中entry维护了ThreadLocal的弱引用,其内存管理


关于强引用 A a = new A(); B b = new B();

C c = new C(b);

如果b = null , 那么在垃圾回收时,并不会将b回收走,原因是在c 内部维护了b的强引用,如果c = null ,那么b 是会被回收走的,还有一种情况就是 WeakReference weak = new WeakReference(b),c的内部使用weak, 这样在b = null 时,b就会被垃圾回收收走。

在ThreadLocalMap当中维护了ThreaLocal的弱引用,当threadLocal为null 时,就可以被回收走,但是ThreadLocalMap还维护了value, 而value是强引用,只有等当前线程消亡了之后,才会被垃圾回收掉,但是如果是在线程池中,线程一直被重复使用,value将永远不会被回收,就会发生内存泄漏,

但是,ThreadLocal中的ThreaLocalMap在setEntry和getEntry,会对key进行判断,如果ThreadLocal为null, 也会将value置为null

但是如果分配了内存之后,并没有调用ThreadLocal中的get, set 或者remove方法,那么key为null的,value值将不会被垃圾回收掉,建议在使用ThreadLocal时,即使进行清理,防止内存泄漏,尤其是在线程池当中使用ThreadLocal.



参考:Java并发编程:深入剖析ThreadLocal

          对ThreadLocal实现原理的一点思考

          深入分析 ThreadLocal 内存泄漏问题

        

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值